From 0f4af48be6e4a7da67a08d239898e8224ed1e301 Mon Sep 17 00:00:00 2001 From: morpheus65535 Date: Thu, 9 Jun 2022 21:02:40 -0400 Subject: [PATCH] Fixed root url redirect not working with base_url defined. #1857 --- bazarr/app/app.py | 25 ++----- bazarr/app/server.py | 6 +- bazarr/app/ui.py | 165 +++++++++++++++++++++++++++++++++++++++++++ bazarr/main.py | 158 +---------------------------------------- 4 files changed, 177 insertions(+), 177 deletions(-) create mode 100644 bazarr/app/ui.py diff --git a/bazarr/app/app.py b/bazarr/app/app.py index 5c3b56086..a303a2bf7 100644 --- a/bazarr/app/app.py +++ b/bazarr/app/app.py @@ -1,8 +1,6 @@ # coding=utf-8 -import os - -from flask import Flask +from flask import Flask, redirect from flask_socketio import SocketIO from .get_args import args @@ -13,12 +11,8 @@ socketio = SocketIO() def create_app(): # Flask Setup - app = Flask(__name__, - template_folder=os.path.join(os.path.dirname(os.path.dirname(__file__)), '..', 'frontend', 'build'), - static_folder=os.path.join(os.path.dirname(os.path.dirname(__file__)), '..', 'frontend', 'build', 'static'), - static_url_path=base_url.rstrip('/') + '/static') + app = Flask(__name__) app.wsgi_app = ReverseProxied(app.wsgi_app) - app.route = prefix_route(app.route, base_url.rstrip('/')) app.config["SECRET_KEY"] = settings.general.flask_secret_key app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True @@ -31,19 +25,14 @@ def create_app(): socketio.init_app(app, path=base_url.rstrip('/')+'/api/socket.io', cors_allowed_origins='*', async_mode='threading', allow_upgrades=False, transports='polling') + + @app.errorhandler(404) + def page_not_found(_): + return redirect(base_url, code=302) + return app -def prefix_route(route_function, prefix='', mask='{0}{1}'): - # Defines a new route function with a prefix. - # The mask argument is a `format string` formatted with, in that order: prefix, route - def newroute(route, *args, **kwargs): - # New function to prefix the route - return route_function(mask.format(prefix, route), *args, **kwargs) - - return newroute - - class ReverseProxied(object): def __init__(self, app): self.app = app diff --git a/bazarr/app/server.py b/bazarr/app/server.py index 12d21d3a5..b80b35cac 100644 --- a/bazarr/app/server.py +++ b/bazarr/app/server.py @@ -8,7 +8,7 @@ import io from waitress.server import create_server from api import api_bp_list - +from .ui import ui_bp from .get_args import args from .config import settings, base_url from .database import database @@ -17,7 +17,9 @@ from .app import create_app app = create_app() for item in api_bp_list: - app.register_blueprint(item, url_prefix=base_url.rstrip('/') + '/api') + ui_bp.register_blueprint(item, url_prefix='/api') + +app.register_blueprint(ui_bp, url_prefix=base_url.rstrip('/')) class Server: diff --git a/bazarr/app/ui.py b/bazarr/app/ui.py new file mode 100644 index 000000000..cfa2e3bad --- /dev/null +++ b/bazarr/app/ui.py @@ -0,0 +1,165 @@ +# coding=utf-8 + +import os +import requests +import mimetypes + +from flask import request, redirect, abort, render_template, Response, session, send_file, stream_with_context, \ + send_from_directory, Blueprint +from functools import wraps +from urllib.parse import unquote + +from constants import headers +from sonarr.info import get_sonarr_info, url_sonarr +from radarr.info import get_radarr_info, url_radarr +from utilities.helper import check_credentials + +from .config import settings, base_url +from .database import System +from .get_args import args + +ui_bp = Blueprint('ui', __name__, + template_folder=os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), + 'frontend', 'build'), + static_folder=os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', + 'build', 'static'), + static_url_path=base_url.rstrip('/') + '/static') + + +def check_login(actual_method): + @wraps(actual_method) + def wrapper(*args, **kwargs): + if settings.auth.type == 'basic': + auth = request.authorization + if not (auth and check_credentials(request.authorization.username, request.authorization.password)): + return ('Unauthorized', 401, { + 'WWW-Authenticate': 'Basic realm="Login Required"' + }) + elif settings.auth.type == 'form': + if 'logged_in' not in session: + return abort(401, message="Unauthorized") + actual_method(*args, **kwargs) + + +@ui_bp.route('/', defaults={'path': ''}) +@ui_bp.route('/') +def catch_all(path): + auth = True + if settings.auth.type == 'basic': + auth = request.authorization + if not (auth and check_credentials(request.authorization.username, request.authorization.password)): + return ('Unauthorized', 401, { + 'WWW-Authenticate': 'Basic realm="Login Required"' + }) + elif settings.auth.type == 'form': + if 'logged_in' not in session: + auth = False + + try: + updated = System.get().updated + except Exception: + updated = '0' + + inject = dict() + inject["baseUrl"] = base_url + inject["canUpdate"] = not args.no_update + inject["hasUpdate"] = updated != '0' + + if auth: + inject["apiKey"] = settings.auth.apikey + + template_url = base_url + if not template_url.endswith("/"): + template_url += "/" + + return render_template("index.html", BAZARR_SERVER_INJECT=inject, baseUrl=template_url) + + +@ui_bp.route('/assets/') +def web_assets(filename): + # forcing mimetypes to prevent bad configuration in Windows registry to prevent Bazarr UI from showing + mimetypes.add_type('application/javascript', '.js') + mimetypes.add_type('text/css', '.css') + mimetypes.add_type('font/woff2', '.woff2') + mimetypes.add_type('image/svg+xml', '.svg') + mimetypes.add_type('image/png', '.png') + mimetypes.add_type('image/x-icon', '.ico') + + # send_from_directory needs an absolute path then we'll use realpath() here + path = os.path.realpath(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', + 'build', 'assets')) + return send_from_directory(path, filename) + + +@check_login +@ui_bp.route('/bazarr.log') +def download_log(): + return send_file(os.path.join(args.config_dir, 'log', 'bazarr.log'), cache_timeout=0, as_attachment=True) + + +@check_login +@ui_bp.route('/images/series/', methods=['GET']) +def series_images(url): + url = url.strip("/") + apikey = settings.sonarr.apikey + baseUrl = settings.sonarr.base_url + if get_sonarr_info.is_legacy(): + url_image = (url_sonarr() + '/api/' + url.lstrip(baseUrl) + '?apikey=' + + apikey).replace('poster-250', 'poster-500') + else: + url_image = (url_sonarr() + '/api/v3/' + url.lstrip(baseUrl) + '?apikey=' + + apikey).replace('poster-250', 'poster-500') + try: + req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers) + except Exception: + return '', 404 + else: + return Response(stream_with_context(req.iter_content(2048)), content_type=req.headers['content-type']) + + +@check_login +@ui_bp.route('/images/movies/', methods=['GET']) +def movies_images(url): + apikey = settings.radarr.apikey + baseUrl = settings.radarr.base_url + if get_radarr_info.is_legacy(): + url_image = url_radarr() + '/api/' + url.lstrip(baseUrl) + '?apikey=' + apikey + else: + url_image = url_radarr() + '/api/v3/' + url.lstrip(baseUrl) + '?apikey=' + apikey + try: + req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers) + except Exception: + return '', 404 + else: + return Response(stream_with_context(req.iter_content(2048)), content_type=req.headers['content-type']) + + +def configured(): + System.update({System.configured: '1'}).execute() + + +@check_login +@ui_bp.route('/test', methods=['GET']) +@ui_bp.route('/test//', methods=['GET']) +def proxy(protocol, url): + url = protocol + '://' + unquote(url) + params = request.args + try: + result = requests.get(url, params, allow_redirects=False, verify=False, timeout=5, headers=headers) + except Exception as e: + return dict(status=False, error=repr(e)) + else: + if result.status_code == 200: + try: + version = result.json()['version'] + return dict(status=True, version=version) + except Exception: + return dict(status=False, error='Error Occurred. Check your settings.') + elif result.status_code == 401: + return dict(status=False, error='Access Denied. Check API key.') + elif result.status_code == 404: + return dict(status=False, error='Cannot get version. Maybe unsupported legacy API call?') + elif 300 <= result.status_code <= 399: + return dict(status=False, error='Wrong URL Base.') + else: + return dict(status=False, error=result.raise_for_status()) diff --git a/bazarr/main.py b/bazarr/main.py index 35c683e8f..9a2b49cc3 100644 --- a/bazarr/main.py +++ b/bazarr/main.py @@ -13,16 +13,8 @@ if os.path.isfile(version_file): os.environ["BAZARR_VERSION"] = bazarr_version.lstrip('v') import app.libs # noqa W0611 -import requests # noqa E402 from threading import Thread # noqa E402 -from functools import wraps # noqa E402 -from urllib.parse import unquote # noqa E402 - -import mimetypes # noqa E402 - -from flask import request, redirect, abort, render_template, Response, session, send_file, stream_with_context, \ - send_from_directory # noqa E402 from app.get_args import args # noqa E402 from app.config import settings, configure_proxy_func, base_url # noqa E402 @@ -32,11 +24,7 @@ from app.notifier import update_notifier # noqa E402 from languages.get_languages import load_language_in_db # noqa E402 from app.signalr_client import sonarr_signalr_client, radarr_signalr_client # noqa E402 from app.check_update import apply_update, check_releases # noqa E402 -from app.server import app, webserver # noqa E402 -from utilities.helper import check_credentials # noqa E402 -from constants import headers # noqa E402 -from sonarr.info import get_sonarr_info, url_sonarr # noqa E402 -from radarr.info import get_radarr_info, url_radarr # noqa E402 +from app.server import webserver # noqa E402 # Install downloaded update if bazarr_version != '': @@ -55,150 +43,6 @@ login_auth = settings.auth.type update_notifier() - -def check_login(actual_method): - @wraps(actual_method) - def wrapper(*args, **kwargs): - if settings.auth.type == 'basic': - auth = request.authorization - if not (auth and check_credentials(request.authorization.username, request.authorization.password)): - return ('Unauthorized', 401, { - 'WWW-Authenticate': 'Basic realm="Login Required"' - }) - elif settings.auth.type == 'form': - if 'logged_in' not in session: - return abort(401, message="Unauthorized") - actual_method(*args, **kwargs) - - -@app.errorhandler(404) -def page_not_found(): - return redirect(base_url, code=302) - - -@app.route('/', defaults={'path': ''}) -@app.route('/') -def catch_all(path): - auth = True - if settings.auth.type == 'basic': - auth = request.authorization - if not (auth and check_credentials(request.authorization.username, request.authorization.password)): - return ('Unauthorized', 401, { - 'WWW-Authenticate': 'Basic realm="Login Required"' - }) - elif settings.auth.type == 'form': - if 'logged_in' not in session: - auth = False - - try: - updated = System.get().updated - except Exception: - updated = '0' - - inject = dict() - inject["baseUrl"] = base_url - inject["canUpdate"] = not args.no_update - inject["hasUpdate"] = updated != '0' - - if auth: - inject["apiKey"] = settings.auth.apikey - - template_url = base_url - if not template_url.endswith("/"): - template_url += "/" - - return render_template("index.html", BAZARR_SERVER_INJECT=inject, baseUrl=template_url) - - -@app.route('/assets/') -def web_assets(filename): - # forcing mimetypes to prevent bad configuration in Windows registry to prevent Bazarr UI from showing - mimetypes.add_type('application/javascript', '.js') - mimetypes.add_type('text/css', '.css') - mimetypes.add_type('font/woff2', '.woff2') - mimetypes.add_type('image/svg+xml', '.svg') - mimetypes.add_type('image/png', '.png') - mimetypes.add_type('image/x-icon', '.ico') - - # send_from_directory needs an absolute path then we'll use realpath() here - path = os.path.realpath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'frontend', 'build', 'assets')) - return send_from_directory(path, filename) - - -@check_login -@app.route('/bazarr.log') -def download_log(): - return send_file(os.path.join(args.config_dir, 'log', 'bazarr.log'), cache_timeout=0, as_attachment=True) - - -@check_login -@app.route('/images/series/', methods=['GET']) -def series_images(url): - url = url.strip("/") - apikey = settings.sonarr.apikey - baseUrl = settings.sonarr.base_url - if get_sonarr_info.is_legacy(): - url_image = (url_sonarr() + '/api/' + url.lstrip(baseUrl) + '?apikey=' + - apikey).replace('poster-250', 'poster-500') - else: - url_image = (url_sonarr() + '/api/v3/' + url.lstrip(baseUrl) + '?apikey=' + - apikey).replace('poster-250', 'poster-500') - try: - req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers) - except Exception: - return '', 404 - else: - return Response(stream_with_context(req.iter_content(2048)), content_type=req.headers['content-type']) - - -@check_login -@app.route('/images/movies/', methods=['GET']) -def movies_images(url): - apikey = settings.radarr.apikey - baseUrl = settings.radarr.base_url - if get_radarr_info.is_legacy(): - url_image = url_radarr() + '/api/' + url.lstrip(baseUrl) + '?apikey=' + apikey - else: - url_image = url_radarr() + '/api/v3/' + url.lstrip(baseUrl) + '?apikey=' + apikey - try: - req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers) - except Exception: - return '', 404 - else: - return Response(stream_with_context(req.iter_content(2048)), content_type=req.headers['content-type']) - - -def configured(): - System.update({System.configured: '1'}).execute() - - -@check_login -@app.route('/test', methods=['GET']) -@app.route('/test//', methods=['GET']) -def proxy(protocol, url): - url = protocol + '://' + unquote(url) - params = request.args - try: - result = requests.get(url, params, allow_redirects=False, verify=False, timeout=5, headers=headers) - except Exception as e: - return dict(status=False, error=repr(e)) - else: - if result.status_code == 200: - try: - version = result.json()['version'] - return dict(status=True, version=version) - except Exception: - return dict(status=False, error='Error Occurred. Check your settings.') - elif result.status_code == 401: - return dict(status=False, error='Access Denied. Check API key.') - elif result.status_code == 404: - return dict(status=False, error='Cannot get version. Maybe unsupported legacy API call?') - elif 300 <= result.status_code <= 399: - return dict(status=False, error='Wrong URL Base.') - else: - return dict(status=False, error=result.raise_for_status()) - - if settings.general.getboolean('use_sonarr'): Thread(target=sonarr_signalr_client.start).start() if settings.general.getboolean('use_radarr'):