Merge remote-tracking branch 'upstream/main' into fix/invalid-token-401

This commit is contained in:
Ben Busby 2022-04-18 12:59:13 -06:00
commit 9ed2c592ea
No known key found for this signature in database
GPG Key ID: B9B7231E01D924A1
12 changed files with 273 additions and 75 deletions

View File

@ -410,7 +410,7 @@ Browser settings:
2. Go to `Default Results` and click the `Setup fallback results` button. Click `+` and add Whoogle, then drag it to the top. 2. Go to `Default Results` and click the `Setup fallback results` button. Click `+` and add Whoogle, then drag it to the top.
- Chrome/Chromium-based Browsers - Chrome/Chromium-based Browsers
- Automatic - Automatic
- Visit the home page of your Whoogle Search instance -- this may automatically add the search engine to your list of search engines. If not, you can add it manually. - Visit the home page of your Whoogle Search instance -- this will automatically add the search engine if the [requirements](https://www.chromium.org/tab-to-search/) are met (GET request, no OnSubmit script, no path). If not, you can add it manually.
- Manual - Manual
- Under search engines > manage search engines > add, manually enter your Whoogle instance details with a `<whoogle url>/search?q=%s` formatted search URL. - Under search engines > manage search engines > add, manually enter your Whoogle instance details with a `<whoogle url>/search?q=%s` formatted search URL.

View File

@ -2,11 +2,12 @@ from app.models.config import Config
from app.models.endpoint import Endpoint from app.models.endpoint import Endpoint
from app.models.g_classes import GClasses from app.models.g_classes import GClasses
from app.request import VALID_PARAMS, MAPS_URL from app.request import VALID_PARAMS, MAPS_URL
from app.utils.misc import read_config_bool from app.utils.misc import get_abs_url, read_config_bool
from app.utils.results import * from app.utils.results import *
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from bs4.element import ResultSet, Tag from bs4.element import ResultSet, Tag
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
import cssutils
from flask import render_template from flask import render_template
import re import re
import urllib.parse as urlparse import urllib.parse as urlparse
@ -53,17 +54,50 @@ def clean_query(query: str) -> str:
return query[:query.find('-site:')] if '-site:' in query else query return query[:query.find('-site:')] if '-site:' in query else query
def clean_css(css: str, page_url: str) -> str:
"""Removes all remote URLs from a CSS string.
Args:
css: The CSS string
Returns:
str: The filtered CSS, with URLs proxied through Whoogle
"""
sheet = cssutils.parseString(css)
urls = cssutils.getUrls(sheet)
for url in urls:
abs_url = get_abs_url(url, page_url)
if abs_url.startswith('data:'):
continue
css = css.replace(
url,
f'/element?type=image/png&url={abs_url}'
)
return css
class Filter: class Filter:
# Limit used for determining if a result is a "regular" result or a list # Limit used for determining if a result is a "regular" result or a list
# type result (such as "people also asked", "related searches", etc) # type result (such as "people also asked", "related searches", etc)
RESULT_CHILD_LIMIT = 7 RESULT_CHILD_LIMIT = 7
def __init__(self, user_key: str, config: Config, mobile=False) -> None: def __init__(
self,
user_key: str,
config: Config,
root_url='',
page_url='',
mobile=False) -> None:
self.config = config self.config = config
self.mobile = mobile self.mobile = mobile
self.user_key = user_key self.user_key = user_key
self.root_url = root_url
self.page_url = page_url
self.main_divs = ResultSet('') self.main_divs = ResultSet('')
self._elements = 0 self._elements = 0
self._av = set()
def __getitem__(self, name): def __getitem__(self, name):
return getattr(self, name) return getattr(self, name)
@ -89,6 +123,7 @@ class Filter:
self.remove_block_titles() self.remove_block_titles()
self.remove_block_url() self.remove_block_url()
self.collapse_sections() self.collapse_sections()
self.update_css(soup)
self.update_styling(soup) self.update_styling(soup)
self.remove_block_tabs(soup) self.remove_block_tabs(soup)
@ -264,7 +299,7 @@ class Filter:
# enabled # enabled
parent.decompose() parent.decompose()
def update_element_src(self, element: Tag, mime: str) -> None: def update_element_src(self, element: Tag, mime: str, attr='src') -> None:
"""Encrypts the original src of an element and rewrites the element src """Encrypts the original src of an element and rewrites the element src
to use the "/element?src=" pass-through. to use the "/element?src=" pass-through.
@ -272,10 +307,12 @@ class Filter:
None (The soup element is modified directly) None (The soup element is modified directly)
""" """
src = element['src'] src = element[attr].split(' ')[0]
if src.startswith('//'): if src.startswith('//'):
src = 'https:' + src src = 'https:' + src
elif src.startswith('data:'):
return
if src.startswith(LOGO_URL): if src.startswith(LOGO_URL):
# Re-brand with Whoogle logo # Re-brand with Whoogle logo
@ -287,9 +324,29 @@ class Filter:
element['src'] = BLANK_B64 element['src'] = BLANK_B64
return return
element['src'] = f'{Endpoint.element}?url=' + self.encrypt_path( element[attr] = f'{self.root_url}/{Endpoint.element}?url=' + (
src, self.encrypt_path(
is_element=True) + '&type=' + urlparse.quote(mime) src,
is_element=True
) + '&type=' + urlparse.quote(mime)
)
def update_css(self, soup) -> None:
"""Updates URLs used in inline styles to be proxied by Whoogle
using the /element endpoint.
Returns:
None (The soup element is modified directly)
"""
# Filter all <style> tags
for style in soup.find_all('style'):
style.string = clean_css(style.string, self.page_url)
# TODO: Convert remote stylesheets to style tags and proxy all
# remote requests
# for link in soup.find_all('link', attrs={'rel': 'stylesheet'}):
# print(link)
def update_styling(self, soup) -> None: def update_styling(self, soup) -> None:
# Remove unnecessary button(s) # Remove unnecessary button(s)
@ -384,9 +441,12 @@ class Filter:
# Strip unneeded arguments # Strip unneeded arguments
link['href'] = filter_link_args(q) link['href'] = filter_link_args(q)
# Add no-js option # Add alternate viewing options for results,
if self.config.nojs: # if the result doesn't already have an AV link
append_nojs(link) netloc = urlparse.urlparse(link['href']).netloc
if self.config.anon_view and netloc not in self._av:
self._av.add(netloc)
append_anon_view(link, self.config)
if self.config.new_tab: if self.config.new_tab:
link['target'] = '_blank' link['target'] = '_blank'

View File

@ -28,6 +28,7 @@ class Config:
self.new_tab = read_config_bool('WHOOGLE_CONFIG_NEW_TAB') self.new_tab = read_config_bool('WHOOGLE_CONFIG_NEW_TAB')
self.view_image = read_config_bool('WHOOGLE_CONFIG_VIEW_IMAGE') self.view_image = read_config_bool('WHOOGLE_CONFIG_VIEW_IMAGE')
self.get_only = read_config_bool('WHOOGLE_CONFIG_GET_ONLY') self.get_only = read_config_bool('WHOOGLE_CONFIG_GET_ONLY')
self.anon_view = read_config_bool('WHOOGLE_CONFIG_ANON_VIEW')
self.accept_language = False self.accept_language = False
self.safe_keys = [ self.safe_keys = [
@ -39,7 +40,9 @@ class Config:
'new_tab', 'new_tab',
'view_image', 'view_image',
'block', 'block',
'safe' 'safe',
'nojs',
'anon_view'
] ]
# Skip setting custom config if there isn't one # Skip setting custom config if there isn't one

View File

@ -16,6 +16,7 @@ from app.models.config import Config
from app.models.endpoint import Endpoint from app.models.endpoint import Endpoint
from app.request import Request, TorError from app.request import Request, TorError
from app.utils.bangs import resolve_bang from app.utils.bangs import resolve_bang
from app.filter import Filter
from app.utils.misc import read_config_bool, get_client_ip, get_request_url, \ from app.utils.misc import read_config_bool, get_client_ip, get_request_url, \
check_for_update check_for_update
from app.utils.results import add_ip_card, bold_search_terms,\ from app.utils.results import add_ip_card, bold_search_terms,\
@ -252,7 +253,7 @@ def opensearch():
'opensearch.xml', 'opensearch.xml',
main_url=opensearch_url, main_url=opensearch_url,
request_type='' if get_only else 'method="post"' request_type='' if get_only else 'method="post"'
), 200, {'Content-Disposition': 'attachment; filename="opensearch.xml"'} ), 200, {'Content-Type': 'application/xml'}
@app.route(f'/{Endpoint.search_html}', methods=['GET']) @app.route(f'/{Endpoint.search_html}', methods=['GET'])
@ -458,15 +459,16 @@ def imgres():
@session_required @session_required
@auth_required @auth_required
def element(): def element():
cipher_suite = Fernet(g.session_key) element_url = src_url = request.args.get('url')
url = request.args.get('url') if element_url.startswith('gAAAAA'):
src_url = "" try:
try: cipher_suite = Fernet(g.session_key)
src_url = cipher_suite.decrypt(url.encode()).decode() src_url = cipher_suite.decrypt(element_url.encode()).decode()
except (InvalidSignature, InvalidToken) as e: print(src_url)
return render_template( except (InvalidSignature, InvalidToken) as e:
'error.html', return render_template(
error_message=str(e)), 401 'error.html',
error_message=str(e)), 401
src_type = request.args.get('type') src_type = request.args.get('type')
@ -486,18 +488,62 @@ def element():
@app.route(f'/{Endpoint.window}') @app.route(f'/{Endpoint.window}')
@session_required
@auth_required @auth_required
def window(): def window():
get_body = g.user_request.send(base_url=request.args.get('location')).text target_url = request.args.get('location')
get_body = get_body.replace('src="/', if target_url.startswith('gAAAAA'):
'src="' + request.args.get('location') + '"') cipher_suite = Fernet(g.session_key)
get_body = get_body.replace('href="/', target_url = cipher_suite.decrypt(target_url.encode()).decode()
'href="' + request.args.get('location') + '"')
content_filter = Filter(
g.session_key,
root_url=request.url_root,
config=g.user_config)
target = urlparse.urlparse(target_url)
host_url = f'{target.scheme}://{target.netloc}'
get_body = g.user_request.send(base_url=target_url).text
results = bsoup(get_body, 'html.parser') results = bsoup(get_body, 'html.parser')
src_attrs = ['src', 'href', 'srcset', 'data-srcset', 'data-src']
for script in results('script'): # Parse HTML response and replace relative links w/ absolute
script.decompose() for element in results.find_all():
for attr in src_attrs:
if not element.has_attr(attr) or not element[attr].startswith('/'):
continue
element[attr] = host_url + element[attr]
# Replace or remove javascript sources
for script in results.find_all('script', {'src': True}):
if 'nojs' in request.args:
script.decompose()
else:
content_filter.update_element_src(script, 'application/javascript')
# Replace all possible image attributes
img_sources = ['src', 'data-src', 'data-srcset', 'srcset']
for img in results.find_all('img'):
_ = [
content_filter.update_element_src(img, 'image/png', attr=_)
for _ in img_sources if img.has_attr(_)
]
# Replace all stylesheet sources
for link in results.find_all('link', {'href': True}):
content_filter.update_element_src(link, 'text/css', attr='href')
# Use anonymous view for all links on page
for a in results.find_all('a', {'href': True}):
a['href'] = '/window?location=' + a['href'] + (
'&nojs=1' if 'nojs' in request.args else '')
# Remove all iframes -- these are commonly used inside of <noscript> tags
# to enforce loading Google Analytics
for iframe in results.find_all('iframe'):
iframe.decompose()
return render_template( return render_template(
'display.html', 'display.html',

View File

@ -22,6 +22,11 @@ li {
color: var(--whoogle-dark-text) !important; color: var(--whoogle-dark-text) !important;
} }
.anon-view {
color: var(--whoogle-dark-text) !important;
text-decoration: underline;
}
textarea { textarea {
background: var(--whoogle-dark-page-bg) !important; background: var(--whoogle-dark-page-bg) !important;
color: var(--whoogle-dark-text) !important; color: var(--whoogle-dark-text) !important;

View File

@ -22,6 +22,11 @@ li {
color: var(--whoogle-text) !important; color: var(--whoogle-text) !important;
} }
.anon-view {
color: var(--whoogle-text) !important;
text-decoration: underline;
}
textarea { textarea {
background: var(--whoogle-page-bg) !important; background: var(--whoogle-page-bg) !important;
color: var(--whoogle-text) !important; color: var(--whoogle-text) !important;

View File

@ -14,7 +14,8 @@
"config-block-url": "Block by URL", "config-block-url": "Block by URL",
"config-block-url-help": "Use regex", "config-block-url-help": "Use regex",
"config-theme": "Theme", "config-theme": "Theme",
"config-nojs": "Show NoJS Links", "config-nojs": "Remove Javascript in Anonymous View",
"config-anon-view": "Show Anonymous View Links",
"config-dark": "Dark Mode", "config-dark": "Dark Mode",
"config-safe": "Safe Search", "config-safe": "Safe Search",
"config-alts": "Replace Social Media Links", "config-alts": "Replace Social Media Links",
@ -41,7 +42,8 @@
"maps": "Maps", "maps": "Maps",
"videos": "Videos", "videos": "Videos",
"news": "News", "news": "News",
"books": "Books" "books": "Books",
"anon-view": "Anonymous View"
}, },
"lang_nl": { "lang_nl": {
"search": "Zoeken", "search": "Zoeken",
@ -58,7 +60,8 @@
"config-block-url": "Blokkeren op URL", "config-block-url": "Blokkeren op URL",
"config-block-url-help": "Gebruik regex", "config-block-url-help": "Gebruik regex",
"config-theme": "Thema", "config-theme": "Thema",
"config-nojs": "Laat NoJS links zien", "config-nojs": "Javascript verwijderen in anonieme weergave",
"config-anon-view": "Toon anonieme links bekijken",
"config-dark": "Donkere Modus", "config-dark": "Donkere Modus",
"config-safe": "Veilig zoeken", "config-safe": "Veilig zoeken",
"config-alts": "Social Media Links Vervangen", "config-alts": "Social Media Links Vervangen",
@ -85,7 +88,8 @@
"maps": "Maps", "maps": "Maps",
"videos": "Videos", "videos": "Videos",
"news": "Nieuws", "news": "Nieuws",
"books": "Boeken" "books": "Boeken",
"anon-view": "Anonieme Weergave"
}, },
"lang_de": { "lang_de": {
"search": "Suchen", "search": "Suchen",
@ -102,7 +106,8 @@
"config-block-url": "Nach URL blockieren", "config-block-url": "Nach URL blockieren",
"config-block-url-help": "Regex verwenden", "config-block-url-help": "Regex verwenden",
"config-theme": "Thema", "config-theme": "Thema",
"config-nojs": "NoJS-Links anzeigen", "config-nojs": "Entfernen Sie Javascript in der anonymen Ansicht",
"config-anon-view": "Anonyme Ansichtslinks anzeigen",
"config-dark": "Dark Mode", "config-dark": "Dark Mode",
"config-safe": "Sicheres Suchen", "config-safe": "Sicheres Suchen",
"config-alts": "Social-Media-Links ersetzen", "config-alts": "Social-Media-Links ersetzen",
@ -129,7 +134,8 @@
"maps": "Maps", "maps": "Maps",
"videos": "Videos", "videos": "Videos",
"news": "Nieuws", "news": "Nieuws",
"books": "Bücher" "books": "Bücher",
"anon-view": "Anonyme Ansicht"
}, },
"lang_es": { "lang_es": {
"search": "Buscar", "search": "Buscar",
@ -146,7 +152,8 @@
"config-block-url": "Bloquear por URL", "config-block-url": "Bloquear por URL",
"config-block-url-help": "Usar expresiones regulares", "config-block-url-help": "Usar expresiones regulares",
"config-theme": "Tema", "config-theme": "Tema",
"config-nojs": "Mostrar Enlaces NoJS", "config-nojs": "Eliminar Javascript en vista anónima",
"config-anon-view": "Mostrar enlaces de vista anónima",
"config-dark": "Modo Oscuro", "config-dark": "Modo Oscuro",
"config-safe": "Búsqueda Segura", "config-safe": "Búsqueda Segura",
"config-alts": "Reemplazar Enlaces de Redes Sociales", "config-alts": "Reemplazar Enlaces de Redes Sociales",
@ -173,7 +180,8 @@
"maps": "Maps", "maps": "Maps",
"videos": "Vídeos", "videos": "Vídeos",
"news": "Noticias", "news": "Noticias",
"books": "Libros" "books": "Libros",
"anon-view": "Vista Anónima"
}, },
"lang_it": { "lang_it": {
"search": "Cerca", "search": "Cerca",
@ -190,7 +198,8 @@
"config-block-url": "Blocca per url", "config-block-url": "Blocca per url",
"config-block-url-help": "Usa regex", "config-block-url-help": "Usa regex",
"config-theme": "Tema", "config-theme": "Tema",
"config-nojs": "Mostra link NoJS", "config-nojs": "Rimuovere Javascript in visualizzazione anonima",
"config-anon-view": "Mostra collegamenti di visualizzazione anonimi",
"config-dark": "Modalità Notte", "config-dark": "Modalità Notte",
"config-safe": "Ricerca Sicura", "config-safe": "Ricerca Sicura",
"config-alts": "Sostituisci link dei social", "config-alts": "Sostituisci link dei social",
@ -217,7 +226,8 @@
"maps": "Maps", "maps": "Maps",
"videos": "Video", "videos": "Video",
"news": "Notizie", "news": "Notizie",
"books": "Libri" "books": "Libri",
"anon-view": "Vista Anonima"
}, },
"lang_pt": { "lang_pt": {
"search": "Pesquisar", "search": "Pesquisar",
@ -234,7 +244,8 @@
"config-block-url": "Bloquear por url", "config-block-url": "Bloquear por url",
"config-block-url-help": "Use regex", "config-block-url-help": "Use regex",
"config-theme": "Tema", "config-theme": "Tema",
"config-nojs": "Mostrar Links NoJS", "config-nojs": "Remover Javascript na visualização anônima",
"config-anon-view": "Mostrar links de visualização anônimos",
"config-dark": "Modo Escuro", "config-dark": "Modo Escuro",
"config-safe": "Pesquisa Segura", "config-safe": "Pesquisa Segura",
"config-alts": "Substituir Links de Redes Sociais", "config-alts": "Substituir Links de Redes Sociais",
@ -261,31 +272,33 @@
"maps": "Maps", "maps": "Maps",
"videos": "Vídeos", "videos": "Vídeos",
"news": "Notícias", "news": "Notícias",
"books": "Livros" "books": "Livros",
"anon-view": "Visualização Anônima"
}, },
"lang_ru": { "lang_ru": {
"search": "Поиск", "search": "Поиск",
"config": "Настройка", "config": "Настройка",
"config-country": "Установить страну", "config-country": "Указать страну",
"config-lang": "Язык интерфейса", "config-lang": "Язык интерфейса",
"config-lang-search": "Язык поиска", "config-lang-search": "Язык поиска",
"config-near": "Около", "config-near": "Около",
"config-near-help": "Название города", "config-near-help": "Название города",
"config-block": "Блокировать", "config-block": "Блокировать",
"config-block-help": "Список сайтов, разделенный запятыми", "config-block-help": "Список сайтов через запятую",
"config-block-title": "Блокировать по названию", "config-block-title": "Блокировать по названию",
"config-block-title-help": "Используйте regex", "config-block-title-help": "Используйте regex",
"config-block-url": "Блокировать по URL-адресу", "config-block-url": "Блокировать по URL-адресу",
"config-block-url-help": "Используйте regex", "config-block-url-help": "Используйте regex",
"config-theme": "Оформление", "config-theme": "Тема",
"config-nojs": "Показывать ссылки NoJS", "config-nojs": "Удалить Javascript в анонимном просмотре",
"config-dark": "Темный режим", "config-anon-view": "Показать ссылки для анонимного просмотра",
"config-dark": "Тёмный режим",
"config-safe": "Безопасный поиск", "config-safe": "Безопасный поиск",
"config-alts": "Заменить ссылки на социальные сети", "config-alts": "Заменить ссылки на социальные сети",
"config-alts-help": "Замена ссылкок Twitter, YouTube, Instagram и т.д. на альтернативы, уважающие конфиденциальность.", "config-alts-help": "Замена ссылкок Twitter, YouTube, Instagram и т.д. на альтернативы, уважающие конфиденциальность.",
"config-new-tab": "Открывать ссылки в новой вкладке", "config-new-tab": "Открывать ссылки в новой вкладке",
"config-images": "Поиск полноразмерных изображений", "config-images": "Поиск полноразмерных изображений",
"config-images-help": "(Экспериментально) Добавляет опцию 'Просмотр изображения' к поиску изображений в ПК-режиме. Это приведет к тому, что миниатюры изображений будут иметь более низкое разрешение.", "config-images-help": "(Эксперимент) Добавляет опцию 'Просмотр изображения' к поиску изображений в ПК-режиме. Это приведет к тому, что миниатюры изображений будут иметь более низкое разрешение.",
"config-tor": "Использовать Tor", "config-tor": "Использовать Tor",
"config-get-only": "Только GET-запросы", "config-get-only": "Только GET-запросы",
"config-url": "Корневой URL-адрес", "config-url": "Корневой URL-адрес",
@ -293,19 +306,20 @@
"load": "Загрузить", "load": "Загрузить",
"apply": "Применить", "apply": "Применить",
"save-as": "Сохранить как...", "save-as": "Сохранить как...",
"github-link": "Посмотреть в GitHub", "github-link": "Посмотреть на GitHub",
"translate": "перевести", "translate": "перевести",
"light": "светлое", "light": "светлая",
"dark": емное", "dark": ёмная",
"system": "системное", "system": "системная",
"ratelimit": "Число экземпляров ограничено", "ratelimit": "Инстанс был ограничен по операциям",
"continue-search": "Продолжайте поиск с Farside", "continue-search": "Продолжить поиск с Farside",
"all": "Все", "all": "Все",
"images": "Картинки", "images": "Картинки",
"maps": "Карты", "maps": "Карты",
"videos": "Видео", "videos": "Видео",
"news": "Новости", "news": "Новости",
"books": "Книги" "books": "Книги",
"anon-view": "Анонимный просмотр"
}, },
"lang_zh-CN": { "lang_zh-CN": {
"search": "搜索", "search": "搜索",
@ -322,7 +336,8 @@
"config-block-url": "按网站链接屏蔽", "config-block-url": "按网站链接屏蔽",
"config-block-url-help": "使用正则表达式", "config-block-url-help": "使用正则表达式",
"config-theme": "主题", "config-theme": "主题",
"config-nojs": "显示 NoJS 链接", "config-nojs": "在匿名视图中删除 Javascript",
"config-anon-view": "显示匿名查看链接",
"config-dark": "深色模式", "config-dark": "深色模式",
"config-safe": "安全搜索", "config-safe": "安全搜索",
"config-alts": "替换社交媒体链接", "config-alts": "替换社交媒体链接",
@ -349,7 +364,8 @@
"maps": "地圖", "maps": "地圖",
"videos": "影片", "videos": "影片",
"news": "新聞", "news": "新聞",
"books": "書籍" "books": "書籍",
"anon-view": "匿名视图"
}, },
"lang_si": { "lang_si": {
"search": "සොයන්න", "search": "සොයන්න",
@ -366,7 +382,8 @@
"config-block-url": "ඒ.ස.නි. මඟින් අවහිර කරන්න", "config-block-url": "ඒ.ස.නි. මඟින් අවහිර කරන්න",
"config-block-url-help": "රෙජෙක්ස් භාවිතා කරන්න", "config-block-url-help": "රෙජෙක්ස් භාවිතා කරන්න",
"config-theme": "තේමාව", "config-theme": "තේමාව",
"config-nojs": "නෝජේඑස් සබැඳි පෙන්වන්න", "config-nojs": "Anonymous View හි Javascript ඉවත් කරන්න",
"config-anon-view": "නිර්නාමික බලන්න සබැඳි පෙන්වන්න",
"config-dark": "අඳුරු ආකාරය", "config-dark": "අඳුරු ආකාරය",
"config-safe": "ආරක්‍ෂිත සෙවුම", "config-safe": "ආරක්‍ෂිත සෙවුම",
"config-alts": "සමාජ මාධ්‍ය සබැඳි ප්‍රතිස්ථාපනය කරන්න", "config-alts": "සමාජ මාධ්‍ය සබැඳි ප්‍රතිස්ථාපනය කරන්න",
@ -393,7 +410,8 @@
"maps": "සිතියම්", "maps": "සිතියම්",
"videos": "වීඩියෝ", "videos": "වීඩියෝ",
"news": "අනුරූප", "news": "අනුරූප",
"books": "පොත්" "books": "පොත්",
"anon-view": "නිර්නාමික දසුන"
}, },
"lang_fr": { "lang_fr": {
"search": "Chercher", "search": "Chercher",
@ -410,7 +428,8 @@
"config-block-url": "Bloquer par URL", "config-block-url": "Bloquer par URL",
"config-block-url-help": "Utiliser l'expression régulière", "config-block-url-help": "Utiliser l'expression régulière",
"config-theme": "Theme", "config-theme": "Theme",
"config-nojs": "Montrer les liens NoJS", "config-nojs": "Supprimer Javascript dans la vue anonyme",
"config-anon-view": "Afficher les liens de vue anonymes",
"config-dark": "Mode Sombre", "config-dark": "Mode Sombre",
"config-safe": "Recherche sécurisée", "config-safe": "Recherche sécurisée",
"config-alts": "Remplacer les liens des réseaux sociaux", "config-alts": "Remplacer les liens des réseaux sociaux",
@ -437,7 +456,8 @@
"maps": "Maps", "maps": "Maps",
"videos": "Vidéos", "videos": "Vidéos",
"news": "Actualités", "news": "Actualités",
"books": "Livres" "books": "Livres",
"anon-view": "Vue anonyme"
}, },
"lang_fa": { "lang_fa": {
"search": "جستجو", "search": "جستجو",
@ -454,7 +474,8 @@
"config-block-url": "بلوک بر اساس URL", "config-block-url": "بلوک بر اساس URL",
"config-block-url-help": "از عبارت منظم استفاده کنید", "config-block-url-help": "از عبارت منظم استفاده کنید",
"config-theme": "پوسته", "config-theme": "پوسته",
"config-nojs": "نمایش پیوند‌های بدون جاوا اسکیریپت", "config-nojs": "جاوا اسکریپت را در نمای ناشناس حذف کنید",
"config-anon-view": "نمایش پیوندهای مشاهده ناشناس",
"config-dark": "حالت تاریک", "config-dark": "حالت تاریک",
"config-safe": "جستجوی امن", "config-safe": "جستجوی امن",
"config-alts": "جایگزینی پیوند‌های شبکه‌های اجتماعی", "config-alts": "جایگزینی پیوند‌های شبکه‌های اجتماعی",
@ -481,7 +502,8 @@
"maps": "نقشه‌ها", "maps": "نقشه‌ها",
"videos": "ویدئوها", "videos": "ویدئوها",
"news": "اخبار", "news": "اخبار",
"books": "کتاب‌ها" "books": "کتاب‌ها",
"anon-view": "نمای ناشناس"
}, },
"lang_cs": { "lang_cs": {
"search": "Hledat", "search": "Hledat",
@ -498,7 +520,8 @@
"config-block-url": "Blokovat podle adresy URL", "config-block-url": "Blokovat podle adresy URL",
"config-block-url-help": "Použijte regulární výraz", "config-block-url-help": "Použijte regulární výraz",
"config-theme": "Motiv", "config-theme": "Motiv",
"config-nojs": "Zobrazit NoJS odkazy", "config-nojs": "Odeberte Javascript v anonymním zobrazení",
"config-anon-view": "Zobrazit odkazy anonymního zobrazení",
"config-dark": "Tmavý motiv", "config-dark": "Tmavý motiv",
"config-safe": "Bezpečné vyhledávání", "config-safe": "Bezpečné vyhledávání",
"config-alts": "Nahradit odkazy na sociální média", "config-alts": "Nahradit odkazy na sociální média",
@ -525,7 +548,8 @@
"maps": "Mapy", "maps": "Mapy",
"videos": "Videa", "videos": "Videa",
"news": "Zprávy", "news": "Zprávy",
"books": "Knihy" "books": "Knihy",
"anon-view": "Anonymní pohled"
}, },
"lang_zh-TW": { "lang_zh-TW": {
"search": "搜尋", "search": "搜尋",
@ -542,7 +566,8 @@
"config-block-url": "按網址屏蔽", "config-block-url": "按網址屏蔽",
"config-block-url-help": "使用正則表達式", "config-block-url-help": "使用正則表達式",
"config-theme": "主題", "config-theme": "主題",
"config-nojs": "顯示 NoJS 連結", "config-nojs": "在匿名視圖中刪除 Javascript",
"config-anon-view": "顯示匿名查看鏈接",
"config-dark": "深色模式", "config-dark": "深色模式",
"config-safe": "安全搜尋", "config-safe": "安全搜尋",
"config-alts": "將社群網站連結換掉", "config-alts": "將社群網站連結換掉",
@ -569,7 +594,8 @@
"maps": "地圖", "maps": "地圖",
"videos": "影片", "videos": "影片",
"news": "新聞", "news": "新聞",
"books": "書籍" "books": "書籍",
"anon-view": "匿名視圖"
}, },
"lang_bg": { "lang_bg": {
"search": "Търсене", "search": "Търсене",
@ -586,7 +612,8 @@
"config-block-url": "Блокиране по url", "config-block-url": "Блокиране по url",
"config-block-url-help": "Използвайте регулярно изражение", "config-block-url-help": "Използвайте регулярно изражение",
"config-theme": "Стил", "config-theme": "Стил",
"config-nojs": "Показване на връзки без JS", "config-nojs": "Премахнете Javascript в анонимен изглед",
"config-anon-view": "Показване на анонимни връзки за преглед",
"config-dark": "Тъмен режим", "config-dark": "Тъмен режим",
"config-safe": "Безопасно търсене", "config-safe": "Безопасно търсене",
"config-alts": "Заменете връзките към социалните медии", "config-alts": "Заменете връзките към социалните медии",
@ -613,7 +640,8 @@
"maps": "Видеоклипове", "maps": "Видеоклипове",
"videos": "Новини", "videos": "Новини",
"news": "Карти", "news": "Карти",
"books": "Книги" "books": "Книги",
"anon-view": "Анонимен изглед"
}, },
"lang_hi": { "lang_hi": {
"search": "खोज", "search": "खोज",
@ -630,7 +658,8 @@
"config-block-url": "url द्वारा अवरोधित करें", "config-block-url": "url द्वारा अवरोधित करें",
"config-block-url-help": "रेगेक्स का प्रयोग करें", "config-block-url-help": "रेगेक्स का प्रयोग करें",
"config-theme": "विषय", "config-theme": "विषय",
"config-nojs": "NoJS लिंक दिखाएं", "config-nojs": "अनाम दृश्य में जावास्क्रिप्ट निकालें",
"config-anon-view": "बेनामी देखें लिंक दिखाएं",
"config-dark": "डार्क मोड", "config-dark": "डार्क मोड",
"config-safe": "सुरक्षित खोज", "config-safe": "सुरक्षित खोज",
"config-alts": "सोशल मीडिया लिंक बदलें", "config-alts": "सोशल मीडिया लिंक बदलें",
@ -657,7 +686,8 @@
"maps": "वीडियो", "maps": "वीडियो",
"videos": "मैप", "videos": "मैप",
"news": "समाचार", "news": "समाचार",
"books": "किताबें" "books": "किताबें",
"anon-view": "अनाम दृश्य"
}, },
"lang_ja": { "lang_ja": {
"search": "検索", "search": "検索",
@ -674,7 +704,8 @@
"config-block-url": "でブロック", "config-block-url": "でブロック",
"config-block-url-help": "正規表現を使用", "config-block-url-help": "正規表現を使用",
"config-theme": "テーマ", "config-theme": "テーマ",
"config-nojs": "非JSリンクを表示", "config-nojs": "匿名ビューでJavascriptを削除する",
"config-anon-view": "匿名のビューリンクを表示する",
"config-dark": "ダークモード", "config-dark": "ダークモード",
"config-safe": "セーフサーチ", "config-safe": "セーフサーチ",
"config-alts": "ソーシャルメディアのリンクを置き換え", "config-alts": "ソーシャルメディアのリンクを置き換え",
@ -701,7 +732,8 @@
"maps": "地図", "maps": "地図",
"videos": "動画", "videos": "動画",
"news": "ニュース", "news": "ニュース",
"books": "書籍" "books": "書籍",
"anon-view": "匿名ビュー"
}, },
"lang_ko": { "lang_ko": {
"search": "검색", "search": "검색",
@ -718,7 +750,8 @@
"config-block-url": "URL로 차단", "config-block-url": "URL로 차단",
"config-block-url-help": "정규 표현식 사용", "config-block-url-help": "정규 표현식 사용",
"config-theme": "테마", "config-theme": "테마",
"config-nojs": "Show NoJS Links", "config-nojs": "익명 보기에서 Javascript 제거",
"config-anon-view": "익명 보기 링크 표시",
"config-dark": "다크 모드", "config-dark": "다크 모드",
"config-safe": "세이프서치", "config-safe": "세이프서치",
"config-alts": "소설 미디어 주소 수정", "config-alts": "소설 미디어 주소 수정",
@ -745,6 +778,7 @@
"maps": "지도", "maps": "지도",
"videos": "동영상", "videos": "동영상",
"news": "뉴스", "news": "뉴스",
"books": "도서" "books": "도서",
"anon-view": "익명 보기"
} }
} }

View File

@ -148,6 +148,10 @@
<input type="text" name="block_url" id="config-block" <input type="text" name="block_url" id="config-block"
placeholder="{{ translation['config-block-url-help'] }}" value="{{ config.block_url }}"> placeholder="{{ translation['config-block-url-help'] }}" value="{{ config.block_url }}">
</div> </div>
<div class="config-div config-div-anon-view">
<label for="config-anon-view">{{ translation['config-anon-view'] }}: </label>
<input type="checkbox" name="anon_view" id="config-anon-view" {{ 'checked' if config.anon_view else '' }}>
</div>
<div class="config-div config-div-nojs"> <div class="config-div config-div-nojs">
<label for="config-nojs">{{ translation['config-nojs'] }}: </label> <label for="config-nojs">{{ translation['config-nojs'] }}: </label>
<input type="checkbox" name="nojs" id="config-nojs" {{ 'checked' if config.nojs else '' }}> <input type="checkbox" name="nojs" id="config-nojs" {{ 'checked' if config.nojs else '' }}>

View File

@ -3,6 +3,7 @@ from flask import Request
import hashlib import hashlib
import os import os
from requests import exceptions, get from requests import exceptions, get
from urllib.parse import urlparse
def gen_file_hash(path: str, static_file: str) -> str: def gen_file_hash(path: str, static_file: str) -> str:
@ -47,3 +48,14 @@ def check_for_update(version_url: str, current: str) -> int:
has_update = '' has_update = ''
return has_update return has_update
def get_abs_url(url, page_url):
# Creates a valid absolute URL using a partial or relative URL
if url.startswith('//'):
return f'https:{url}'
elif url.startswith('/'):
return f'{urlparse(page_url).netloc}{url}'
elif url.startswith('./'):
return f'{page_url}{url[2:]}'
return url

View File

@ -1,6 +1,8 @@
from app.models.config import Config
from app.models.endpoint import Endpoint from app.models.endpoint import Endpoint
from bs4 import BeautifulSoup, NavigableString from bs4 import BeautifulSoup, NavigableString
import copy import copy
from flask import current_app
import html import html
import os import os
import urllib.parse as urlparse import urllib.parse as urlparse
@ -183,11 +185,35 @@ def append_nojs(result: BeautifulSoup) -> None:
""" """
nojs_link = BeautifulSoup(features='html.parser').new_tag('a') nojs_link = BeautifulSoup(features='html.parser').new_tag('a')
nojs_link['href'] = f'/{Endpoint.window}?location=' + result['href'] nojs_link['href'] = f'/{Endpoint.window}?nojs=1&location=' + result['href']
nojs_link.string = ' NoJS Link' nojs_link.string = ' NoJS Link'
result.append(nojs_link) result.append(nojs_link)
def append_anon_view(result: BeautifulSoup, config: Config) -> None:
"""Appends an 'anonymous view' for a search result, where all site
contents are viewed through Whoogle as a proxy.
Args:
result: The search result to append an anon view link to
nojs: Remove Javascript from Anonymous View
Returns:
None
"""
av_link = BeautifulSoup(features='html.parser').new_tag('a')
nojs = 'nojs=1' if config.nojs else 'nojs=0'
location = f'location={result["href"]}'
av_link['href'] = f'/{Endpoint.window}?{nojs}&{location}'
translation = current_app.config['TRANSLATIONS'][
config.get_localization_lang()
]
av_link.string = f'{translation["config-anon-view"]}'
av_link['class'] = 'anon-view'
result.append(av_link)
def add_ip_card(html_soup: BeautifulSoup, ip: str) -> BeautifulSoup: def add_ip_card(html_soup: BeautifulSoup, ip: str) -> BeautifulSoup:
"""Adds the client's IP address to the search results """Adds the client's IP address to the search results
if query contains keywords if query contains keywords

View File

@ -56,6 +56,7 @@ class Search:
""" """
def __init__(self, request, config, session_key, cookies_disabled=False): def __init__(self, request, config, session_key, cookies_disabled=False):
method = request.method method = request.method
self.request = request
self.request_params = request.args if method == 'GET' else request.form self.request_params = request.args if method == 'GET' else request.form
self.user_agent = request.headers.get('User-Agent') self.user_agent = request.headers.get('User-Agent')
self.feeling_lucky = False self.feeling_lucky = False
@ -115,6 +116,7 @@ class Search:
mobile = 'Android' in self.user_agent or 'iPhone' in self.user_agent mobile = 'Android' in self.user_agent or 'iPhone' in self.user_agent
content_filter = Filter(self.session_key, content_filter = Filter(self.session_key,
root_url=self.request.url_root,
mobile=mobile, mobile=mobile,
config=self.config) config=self.config)
full_query = gen_query(self.query, full_query = gen_query(self.query,

View File

@ -6,6 +6,7 @@ cffi==1.15.0
chardet==3.0.4 chardet==3.0.4
click==8.0.3 click==8.0.3
cryptography==3.3.2 cryptography==3.3.2
cssutils==2.4.0
defusedxml==0.7.1 defusedxml==0.7.1
Flask==1.1.1 Flask==1.1.1
Flask-Session==0.4.0 Flask-Session==0.4.0