diff --git a/bazarr/main.py b/bazarr/main.py index 2725be325..7dbea5265 100644 --- a/bazarr/main.py +++ b/bazarr/main.py @@ -14,7 +14,6 @@ import six from six.moves import zip from functools import reduce -# import bottle import itertools import operator import pretty @@ -43,12 +42,10 @@ from cherrypy.wsgiserver import CherryPyWSGIServer from io import BytesIO from six import text_type -from beaker.middleware import SessionMiddleware -from cork import Cork -# from bottle import route, template, static_file, request, redirect, response, HTTPError, app, hook, abort from datetime import timedelta from get_languages import load_language_in_db, language_from_alpha3, language_from_alpha2, alpha2_from_alpha3 from flask import Flask, make_response, request, redirect, abort, render_template +from flask_cors import CORS from get_providers import get_providers, get_providers_auth, list_throttled_providers from get_series import * @@ -74,6 +71,9 @@ gc.enable() # Flask Setup app = Flask(__name__, template_folder=os.path.join(os.path.dirname(__file__), '..', 'views'), static_folder=os.path.join(os.path.dirname(__file__), '..', 'static')) +# Add Cors +CORS(app) + # Check and install update on startup when running on Windows from installer if args.release_update: check_and_apply_update() @@ -88,20 +88,12 @@ if settings.proxy.type != 'None': os.environ['HTTPS_PROXY'] = str(proxy) os.environ['NO_PROXY'] = str(settings.proxy.exclude) -# bottle.TEMPLATE_PATH.insert(0, os.path.join(os.path.dirname(__file__), '../views/')) -# if "PYCHARM_HOSTED" in os.environ: -# bottle.debug(True) -# bottle.TEMPLATES.clear() -# else: -# bottle.ERROR_PAGE_TEMPLATE = bottle.ERROR_PAGE_TEMPLATE.replace('if DEBUG and', 'if') - # Reset restart required warning on start database.execute("UPDATE system SET configured='0', updated='0'") # Load languages in database load_language_in_db() -aaa = Cork(os.path.normpath(os.path.join(args.config_dir, 'config'))) # app = app() # session_opts = { @@ -115,7 +107,6 @@ aaa = Cork(os.path.normpath(os.path.join(args.config_dir, 'config'))) # app = SessionMiddleware(app, session_opts) login_auth = settings.auth.type - update_notifier() @@ -159,12 +150,6 @@ def post_get(name, default=''): return request.POST.get(name, default).strip() -# @app.before_request -# def enable_cors(): -# if request: -# request.headers['Access-Control-Allow-Origin'] = '*' -## todo: add cors lib - @app.route(base_url + 'login/') def login_form(): msg = request.query.get('msg', '') @@ -173,27 +158,17 @@ def login_form(): @app.route(base_url + 'login/', methods=['POST']) def login(): - aaa = Cork(os.path.normpath(os.path.join(args.config_dir, 'config'))) username = post_get('username') password = post_get('password') - aaa.login(username, password, success_redirect=base_url, fail_redirect=(base_url + 'login?msg=fail')) + if check_credentials(username, password): + return redirect(base_url) + else: + return redirect(base_url + 'login?msg=fail') @app.route(base_url + 'logout/') def logout(): - if settings.auth.type == 'form': - aaa.logout(success_redirect=(base_url + 'login')) - elif settings.auth.type == 'basic': - abort(401) - else: - aaa.logout(success_redirect=(base_url)) - - -# @app.route('/') -# # @custom_auth_basic(check_credentials) -# def redirect_root(): -# -# redirect(base_url) + abort(401) @app.route(base_url + 'shutdown/') @@ -238,7 +213,7 @@ def restart(): @app.route(base_url + 'wizard/') -# @custom_auth_basic(check_credentials) +@custom_auth_basic(check_credentials) def wizard(): diff --git a/libs/click/__init__.py b/libs/click/__init__.py index 971e55d0a..d3c33660a 100644 --- a/libs/click/__init__.py +++ b/libs/click/__init__.py @@ -1,17 +1,15 @@ # -*- coding: utf-8 -*- """ - click - ~~~~~ +click +~~~~~ - Click is a simple Python module that wraps the stdlib's optparse to make - writing command line scripts fun. Unlike other modules, it's based around - a simple API that does not come with too much magic and is composable. +Click is a simple Python module inspired by the stdlib optparse to make +writing command line scripts fun. Unlike other modules, it's based +around a simple API that does not come with too much magic and is +composable. - In case optparse ever gets removed from the stdlib, it will be shipped by - this module. - - :copyright: (c) 2014 by Armin Ronacher. - :license: BSD, see LICENSE for more details. +:copyright: © 2014 by the Pallets team. +:license: BSD, see LICENSE.rst for more details. """ # Core classes @@ -28,7 +26,7 @@ from .decorators import pass_context, pass_obj, make_pass_decorator, \ # Types from .types import ParamType, File, Path, Choice, IntRange, Tuple, \ - STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED + DateTime, STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED, FloatRange # Utilities from .utils import echo, get_binary_stream, get_text_stream, open_file, \ @@ -65,8 +63,9 @@ __all__ = [ 'version_option', 'help_option', # Types - 'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', 'STRING', - 'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED', + 'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', + 'DateTime', 'STRING', 'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED', + 'FloatRange', # Utilities 'echo', 'get_binary_stream', 'get_text_stream', 'open_file', @@ -95,4 +94,4 @@ __all__ = [ disable_unicode_literals_warning = False -__version__ = '6.7' +__version__ = '7.0' diff --git a/libs/flask_cors/__init__.py b/libs/flask_cors/__init__.py new file mode 100644 index 000000000..c84a763f8 --- /dev/null +++ b/libs/flask_cors/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +""" + flask_cors + ~~~~ + Flask-CORS is a simple extension to Flask allowing you to support cross + origin resource sharing (CORS) using a simple decorator. + + :copyright: (c) 2016 by Cory Dolphin. + :license: MIT, see LICENSE for more details. +""" +from .decorator import cross_origin +from .extension import CORS +from .version import __version__ + +__all__ = ['CORS', 'cross_origin'] + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +# Set initial level to WARN. Users must manually enable logging for +# flask_cors to see our logging. +rootlogger = logging.getLogger(__name__) +rootlogger.addHandler(NullHandler()) + +if rootlogger.level == logging.NOTSET: + rootlogger.setLevel(logging.WARN) \ No newline at end of file diff --git a/libs/flask_cors/core.py b/libs/flask_cors/core.py new file mode 100644 index 000000000..f29aeb4f8 --- /dev/null +++ b/libs/flask_cors/core.py @@ -0,0 +1,383 @@ +# -*- coding: utf-8 -*- +""" + core + ~~~~ + Core functionality shared between the extension and the decorator. + + :copyright: (c) 2016 by Cory Dolphin. + :license: MIT, see LICENSE for more details. +""" +import re +import logging +try: + # on python 3 + from collections.abc import Iterable +except ImportError: + # on python 2.7 and pypy + from collections import Iterable +from datetime import timedelta +from six import string_types +from flask import request, current_app +from werkzeug.datastructures import Headers, MultiDict + +LOG = logging.getLogger(__name__) + +# Response Headers +ACL_ORIGIN = 'Access-Control-Allow-Origin' +ACL_METHODS = 'Access-Control-Allow-Methods' +ACL_ALLOW_HEADERS = 'Access-Control-Allow-Headers' +ACL_EXPOSE_HEADERS = 'Access-Control-Expose-Headers' +ACL_CREDENTIALS = 'Access-Control-Allow-Credentials' +ACL_MAX_AGE = 'Access-Control-Max-Age' + +# Request Header +ACL_REQUEST_METHOD = 'Access-Control-Request-Method' +ACL_REQUEST_HEADERS = 'Access-Control-Request-Headers' + +ALL_METHODS = ['GET', 'HEAD', 'POST', 'OPTIONS', 'PUT', 'PATCH', 'DELETE'] +CONFIG_OPTIONS = ['CORS_ORIGINS', 'CORS_METHODS', 'CORS_ALLOW_HEADERS', + 'CORS_EXPOSE_HEADERS', 'CORS_SUPPORTS_CREDENTIALS', + 'CORS_MAX_AGE', 'CORS_SEND_WILDCARD', + 'CORS_AUTOMATIC_OPTIONS', 'CORS_VARY_HEADER', + 'CORS_RESOURCES', 'CORS_INTERCEPT_EXCEPTIONS', + 'CORS_ALWAYS_SEND'] +# Attribute added to request object by decorator to indicate that CORS +# was evaluated, in case the decorator and extension are both applied +# to a view. +FLASK_CORS_EVALUATED = '_FLASK_CORS_EVALUATED' + +# Strange, but this gets the type of a compiled regex, which is otherwise not +# exposed in a public API. +RegexObject = type(re.compile('')) +DEFAULT_OPTIONS = dict(origins='*', + methods=ALL_METHODS, + allow_headers='*', + expose_headers=None, + supports_credentials=False, + max_age=None, + send_wildcard=False, + automatic_options=True, + vary_header=True, + resources=r'/*', + intercept_exceptions=True, + always_send=True) + + +def parse_resources(resources): + if isinstance(resources, dict): + # To make the API more consistent with the decorator, allow a + # resource of '*', which is not actually a valid regexp. + resources = [(re_fix(k), v) for k, v in resources.items()] + + # Sort by regex length to provide consistency of matching and + # to provide a proxy for specificity of match. E.G. longer + # regular expressions are tried first. + def pattern_length(pair): + maybe_regex, _ = pair + return len(get_regexp_pattern(maybe_regex)) + + return sorted(resources, + key=pattern_length, + reverse=True) + + elif isinstance(resources, string_types): + return [(re_fix(resources), {})] + + elif isinstance(resources, Iterable): + return [(re_fix(r), {}) for r in resources] + + # Type of compiled regex is not part of the public API. Test for this + # at runtime. + elif isinstance(resources, RegexObject): + return [(re_fix(resources), {})] + + else: + raise ValueError("Unexpected value for resources argument.") + + +def get_regexp_pattern(regexp): + """ + Helper that returns regexp pattern from given value. + + :param regexp: regular expression to stringify + :type regexp: _sre.SRE_Pattern or str + :returns: string representation of given regexp pattern + :rtype: str + """ + try: + return regexp.pattern + except AttributeError: + return str(regexp) + + +def get_cors_origins(options, request_origin): + origins = options.get('origins') + wildcard = r'.*' in origins + + # If the Origin header is not present terminate this set of steps. + # The request is outside the scope of this specification.-- W3Spec + if request_origin: + LOG.debug("CORS request received with 'Origin' %s", request_origin) + + # If the allowed origins is an asterisk or 'wildcard', always match + if wildcard and options.get('send_wildcard'): + LOG.debug("Allowed origins are set to '*'. Sending wildcard CORS header.") + return ['*'] + # If the value of the Origin header is a case-sensitive match + # for any of the values in list of origins + elif try_match_any(request_origin, origins): + LOG.debug("The request's Origin header matches. Sending CORS headers.", ) + # Add a single Access-Control-Allow-Origin header, with either + # the value of the Origin header or the string "*" as value. + # -- W3Spec + return [request_origin] + else: + LOG.debug("The request's Origin header does not match any of allowed origins.") + return None + + + elif options.get('always_send'): + if wildcard: + # If wildcard is in the origins, even if 'send_wildcard' is False, + # simply send the wildcard. Unless supports_credentials is True, + # since that is forbidded by the spec.. + # It is the most-likely to be correct thing to do (the only other + # option is to return nothing, which almost certainly not what + # the developer wants if the '*' origin was specified. + if options.get('supports_credentials'): + return None + else: + return ['*'] + else: + # Return all origins that are not regexes. + return sorted([o for o in origins if not probably_regex(o)]) + + # Terminate these steps, return the original request untouched. + else: + LOG.debug("The request did not contain an 'Origin' header. This means the browser or client did not request CORS, ensure the Origin Header is set.") + return None + + +def get_allow_headers(options, acl_request_headers): + if acl_request_headers: + request_headers = [h.strip() for h in acl_request_headers.split(',')] + + # any header that matches in the allow_headers + matching_headers = filter( + lambda h: try_match_any(h, options.get('allow_headers')), + request_headers + ) + + return ', '.join(sorted(matching_headers)) + + return None + + +def get_cors_headers(options, request_headers, request_method): + origins_to_set = get_cors_origins(options, request_headers.get('Origin')) + headers = MultiDict() + + if not origins_to_set: # CORS is not enabled for this route + return headers + + for origin in origins_to_set: + headers.add(ACL_ORIGIN, origin) + + headers[ACL_EXPOSE_HEADERS] = options.get('expose_headers') + + if options.get('supports_credentials'): + headers[ACL_CREDENTIALS] = 'true' # case sensative + + # This is a preflight request + # http://www.w3.org/TR/cors/#resource-preflight-requests + if request_method == 'OPTIONS': + acl_request_method = request_headers.get(ACL_REQUEST_METHOD, '').upper() + + # If there is no Access-Control-Request-Method header or if parsing + # failed, do not set any additional headers + if acl_request_method and acl_request_method in options.get('methods'): + + # If method is not a case-sensitive match for any of the values in + # list of methods do not set any additional headers and terminate + # this set of steps. + headers[ACL_ALLOW_HEADERS] = get_allow_headers(options, request_headers.get(ACL_REQUEST_HEADERS)) + headers[ACL_MAX_AGE] = options.get('max_age') + headers[ACL_METHODS] = options.get('methods') + else: + LOG.info("The request's Access-Control-Request-Method header does not match allowed methods. CORS headers will not be applied.") + + # http://www.w3.org/TR/cors/#resource-implementation + if options.get('vary_header'): + # Only set header if the origin returned will vary dynamically, + # i.e. if we are not returning an asterisk, and there are multiple + # origins that can be matched. + if headers[ACL_ORIGIN] == '*': + pass + elif (len(options.get('origins')) > 1 or + len(origins_to_set) > 1 or + any(map(probably_regex, options.get('origins')))): + headers.add('Vary', 'Origin') + + return MultiDict((k, v) for k, v in headers.items() if v) + + +def set_cors_headers(resp, options): + """ + Performs the actual evaluation of Flas-CORS options and actually + modifies the response object. + + This function is used both in the decorator and the after_request + callback + """ + + # If CORS has already been evaluated via the decorator, skip + if hasattr(resp, FLASK_CORS_EVALUATED): + LOG.debug('CORS have been already evaluated, skipping') + return resp + + # Some libraries, like OAuthlib, set resp.headers to non Multidict + # objects (Werkzeug Headers work as well). This is a problem because + # headers allow repeated values. + if (not isinstance(resp.headers, Headers) + and not isinstance(resp.headers, MultiDict)): + resp.headers = MultiDict(resp.headers) + + headers_to_set = get_cors_headers(options, request.headers, request.method) + + LOG.debug('Settings CORS headers: %s', str(headers_to_set)) + + for k, v in headers_to_set.items(): + resp.headers.add(k, v) + + return resp + +def probably_regex(maybe_regex): + if isinstance(maybe_regex, RegexObject): + return True + else: + common_regex_chars = ['*', '\\', ']', '?', '$', '^', '[', ']', '(', ')'] + # Use common characters used in regular expressions as a proxy + # for if this string is in fact a regex. + return any((c in maybe_regex for c in common_regex_chars)) + +def re_fix(reg): + """ + Replace the invalid regex r'*' with the valid, wildcard regex r'/.*' to + enable the CORS app extension to have a more user friendly api. + """ + return r'.*' if reg == r'*' else reg + + +def try_match_any(inst, patterns): + return any(try_match(inst, pattern) for pattern in patterns) + + +def try_match(request_origin, maybe_regex): + """Safely attempts to match a pattern or string to a request origin.""" + if isinstance(maybe_regex, RegexObject): + return re.match(maybe_regex, request_origin) + elif probably_regex(maybe_regex): + return re.match(maybe_regex, request_origin, flags=re.IGNORECASE) + else: + try: + return request_origin.lower() == maybe_regex.lower() + except AttributeError: + return request_origin == maybe_regex + + +def get_cors_options(appInstance, *dicts): + """ + Compute CORS options for an application by combining the DEFAULT_OPTIONS, + the app's configuration-specified options and any dictionaries passed. The + last specified option wins. + """ + options = DEFAULT_OPTIONS.copy() + options.update(get_app_kwarg_dict(appInstance)) + if dicts: + for d in dicts: + options.update(d) + + return serialize_options(options) + + +def get_app_kwarg_dict(appInstance=None): + """Returns the dictionary of CORS specific app configurations.""" + app = (appInstance or current_app) + + # In order to support blueprints which do not have a config attribute + app_config = getattr(app, 'config', {}) + + return { + k.lower().replace('cors_', ''): app_config.get(k) + for k in CONFIG_OPTIONS + if app_config.get(k) is not None + } + + +def flexible_str(obj): + """ + A more flexible str function which intelligently handles stringifying + strings, lists and other iterables. The results are lexographically sorted + to ensure generated responses are consistent when iterables such as Set + are used. + """ + if obj is None: + return None + elif(not isinstance(obj, string_types) + and isinstance(obj, Iterable)): + return ', '.join(str(item) for item in sorted(obj)) + else: + return str(obj) + + +def serialize_option(options_dict, key, upper=False): + if key in options_dict: + value = flexible_str(options_dict[key]) + options_dict[key] = value.upper() if upper else value + + +def ensure_iterable(inst): + """ + Wraps scalars or string types as a list, or returns the iterable instance. + """ + if isinstance(inst, string_types): + return [inst] + elif not isinstance(inst, Iterable): + return [inst] + else: + return inst + +def sanitize_regex_param(param): + return [re_fix(x) for x in ensure_iterable(param)] + + +def serialize_options(opts): + """ + A helper method to serialize and processes the options dictionary. + """ + options = (opts or {}).copy() + + for key in opts.keys(): + if key not in DEFAULT_OPTIONS: + LOG.warning("Unknown option passed to Flask-CORS: %s", key) + + # Ensure origins is a list of allowed origins with at least one entry. + options['origins'] = sanitize_regex_param(options.get('origins')) + options['allow_headers'] = sanitize_regex_param(options.get('allow_headers')) + + # This is expressly forbidden by the spec. Raise a value error so people + # don't get burned in production. + if r'.*' in options['origins'] and options['supports_credentials'] and options['send_wildcard']: + raise ValueError("Cannot use supports_credentials in conjunction with" + "an origin string of '*'. See: " + "http://www.w3.org/TR/cors/#resource-requests") + + + + serialize_option(options, 'expose_headers') + serialize_option(options, 'methods', upper=True) + + if isinstance(options.get('max_age'), timedelta): + options['max_age'] = str(int(options['max_age'].total_seconds())) + + return options diff --git a/libs/flask_cors/decorator.py b/libs/flask_cors/decorator.py new file mode 100644 index 000000000..4a529e7be --- /dev/null +++ b/libs/flask_cors/decorator.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +""" + decorator + ~~~~ + This unit exposes a single decorator which should be used to wrap a + Flask route with. It accepts all parameters and options as + the CORS extension. + + :copyright: (c) 2016 by Cory Dolphin. + :license: MIT, see LICENSE for more details. +""" +from functools import update_wrapper +from flask import make_response, request, current_app +from .core import * + +LOG = logging.getLogger(__name__) + +def cross_origin(*args, **kwargs): + """ + This function is the decorator which is used to wrap a Flask route with. + In the simplest case, simply use the default parameters to allow all + origins in what is the most permissive configuration. If this method + modifies state or performs authentication which may be brute-forced, you + should add some degree of protection, such as Cross Site Forgery + Request protection. + + :param origins: + The origin, or list of origins to allow requests from. + The origin(s) may be regular expressions, case-sensitive strings, + or else an asterisk + + Default : '*' + :type origins: list, string or regex + + :param methods: + The method or list of methods which the allowed origins are allowed to + access for non-simple requests. + + Default : [GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE] + :type methods: list or string + + :param expose_headers: + The header or list which are safe to expose to the API of a CORS API + specification. + + Default : None + :type expose_headers: list or string + + :param allow_headers: + The header or list of header field names which can be used when this + resource is accessed by allowed origins. The header(s) may be regular + expressions, case-sensitive strings, or else an asterisk. + + Default : '*', allow all headers + :type allow_headers: list, string or regex + + :param supports_credentials: + Allows users to make authenticated requests. If true, injects the + `Access-Control-Allow-Credentials` header in responses. This allows + cookies and credentials to be submitted across domains. + + :note: This option cannot be used in conjuction with a '*' origin + + Default : False + :type supports_credentials: bool + + :param max_age: + The maximum time for which this CORS request maybe cached. This value + is set as the `Access-Control-Max-Age` header. + + Default : None + :type max_age: timedelta, integer, string or None + + :param send_wildcard: If True, and the origins parameter is `*`, a wildcard + `Access-Control-Allow-Origin` header is sent, rather than the + request's `Origin` header. + + Default : False + :type send_wildcard: bool + + :param vary_header: + If True, the header Vary: Origin will be returned as per the W3 + implementation guidelines. + + Setting this header when the `Access-Control-Allow-Origin` is + dynamically generated (e.g. when there is more than one allowed + origin, and an Origin than '*' is returned) informs CDNs and other + caches that the CORS headers are dynamic, and cannot be cached. + + If False, the Vary header will never be injected or altered. + + Default : True + :type vary_header: bool + + :param automatic_options: + Only applies to the `cross_origin` decorator. If True, Flask-CORS will + override Flask's default OPTIONS handling to return CORS headers for + OPTIONS requests. + + Default : True + :type automatic_options: bool + + """ + _options = kwargs + + def decorator(f): + LOG.debug("Enabling %s for cross_origin using options:%s", f, _options) + + # If True, intercept OPTIONS requests by modifying the view function, + # replicating Flask's default behavior, and wrapping the response with + # CORS headers. + # + # If f.provide_automatic_options is unset or True, Flask's route + # decorator (which is actually wraps the function object we return) + # intercepts OPTIONS handling, and requests will not have CORS headers + if _options.get('automatic_options', True): + f.required_methods = getattr(f, 'required_methods', set()) + f.required_methods.add('OPTIONS') + f.provide_automatic_options = False + + def wrapped_function(*args, **kwargs): + # Handle setting of Flask-Cors parameters + options = get_cors_options(current_app, _options) + + if options.get('automatic_options') and request.method == 'OPTIONS': + resp = current_app.make_default_options_response() + else: + resp = make_response(f(*args, **kwargs)) + + set_cors_headers(resp, options) + setattr(resp, FLASK_CORS_EVALUATED, True) + return resp + + return update_wrapper(wrapped_function, f) + return decorator diff --git a/libs/flask_cors/extension.py b/libs/flask_cors/extension.py new file mode 100644 index 000000000..6a585aa38 --- /dev/null +++ b/libs/flask_cors/extension.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +""" + extension + ~~~~ + Flask-CORS is a simple extension to Flask allowing you to support cross + origin resource sharing (CORS) using a simple decorator. + + :copyright: (c) 2016 by Cory Dolphin. + :license: MIT, see LICENSE for more details. +""" +from flask import request +from .core import * + +LOG = logging.getLogger(__name__) + +class CORS(object): + """ + Initializes Cross Origin Resource sharing for the application. The + arguments are identical to :py:func:`cross_origin`, with the addition of a + `resources` parameter. The resources parameter defines a series of regular + expressions for resource paths to match and optionally, the associated + options to be applied to the particular resource. These options are + identical to the arguments to :py:func:`cross_origin`. + + The settings for CORS are determined in the following order + + 1. Resource level settings (e.g when passed as a dictionary) + 2. Keyword argument settings + 3. App level configuration settings (e.g. CORS_*) + 4. Default settings + + Note: as it is possible for multiple regular expressions to match a + resource path, the regular expressions are first sorted by length, + from longest to shortest, in order to attempt to match the most + specific regular expression. This allows the definition of a + number of specific resource options, with a wildcard fallback + for all other resources. + + :param resources: + The series of regular expression and (optionally) associated CORS + options to be applied to the given resource path. + + If the argument is a dictionary, it's keys must be regular expressions, + and the values must be a dictionary of kwargs, identical to the kwargs + of this function. + + If the argument is a list, it is expected to be a list of regular + expressions, for which the app-wide configured options are applied. + + If the argument is a string, it is expected to be a regular expression + for which the app-wide configured options are applied. + + Default : Match all and apply app-level configuration + + :type resources: dict, iterable or string + + :param origins: + The origin, or list of origins to allow requests from. + The origin(s) may be regular expressions, case-sensitive strings, + or else an asterisk + + Default : '*' + :type origins: list, string or regex + + :param methods: + The method or list of methods which the allowed origins are allowed to + access for non-simple requests. + + Default : [GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE] + :type methods: list or string + + :param expose_headers: + The header or list which are safe to expose to the API of a CORS API + specification. + + Default : None + :type expose_headers: list or string + + :param allow_headers: + The header or list of header field names which can be used when this + resource is accessed by allowed origins. The header(s) may be regular + expressions, case-sensitive strings, or else an asterisk. + + Default : '*', allow all headers + :type allow_headers: list, string or regex + + :param supports_credentials: + Allows users to make authenticated requests. If true, injects the + `Access-Control-Allow-Credentials` header in responses. This allows + cookies and credentials to be submitted across domains. + + :note: This option cannot be used in conjuction with a '*' origin + + Default : False + :type supports_credentials: bool + + :param max_age: + The maximum time for which this CORS request maybe cached. This value + is set as the `Access-Control-Max-Age` header. + + Default : None + :type max_age: timedelta, integer, string or None + + :param send_wildcard: If True, and the origins parameter is `*`, a wildcard + `Access-Control-Allow-Origin` header is sent, rather than the + request's `Origin` header. + + Default : False + :type send_wildcard: bool + + :param vary_header: + If True, the header Vary: Origin will be returned as per the W3 + implementation guidelines. + + Setting this header when the `Access-Control-Allow-Origin` is + dynamically generated (e.g. when there is more than one allowed + origin, and an Origin than '*' is returned) informs CDNs and other + caches that the CORS headers are dynamic, and cannot be cached. + + If False, the Vary header will never be injected or altered. + + Default : True + :type vary_header: bool + """ + + def __init__(self, app=None, **kwargs): + self._options = kwargs + if app is not None: + self.init_app(app, **kwargs) + + def init_app(self, app, **kwargs): + # The resources and options may be specified in the App Config, the CORS constructor + # or the kwargs to the call to init_app. + options = get_cors_options(app, self._options, kwargs) + + # Flatten our resources into a list of the form + # (pattern_or_regexp, dictionary_of_options) + resources = parse_resources(options.get('resources')) + + # Compute the options for each resource by combining the options from + # the app's configuration, the constructor, the kwargs to init_app, and + # finally the options specified in the resources dictionary. + resources = [ + (pattern, get_cors_options(app, options, opts)) + for (pattern, opts) in resources + ] + + # Create a human readable form of these resources by converting the compiled + # regular expressions into strings. + resources_human = {get_regexp_pattern(pattern): opts for (pattern,opts) in resources} + LOG.debug("Configuring CORS with resources: %s", resources_human) + + cors_after_request = make_after_request_function(resources) + app.after_request(cors_after_request) + + # Wrap exception handlers with cross_origin + # These error handlers will still respect the behavior of the route + if options.get('intercept_exceptions', True): + def _after_request_decorator(f): + def wrapped_function(*args, **kwargs): + return cors_after_request(app.make_response(f(*args, **kwargs))) + return wrapped_function + + if hasattr(app, 'handle_exception'): + app.handle_exception = _after_request_decorator( + app.handle_exception) + app.handle_user_exception = _after_request_decorator( + app.handle_user_exception) + +def make_after_request_function(resources): + def cors_after_request(resp): + # If CORS headers are set in a view decorator, pass + if resp.headers is not None and resp.headers.get(ACL_ORIGIN): + LOG.debug('CORS have been already evaluated, skipping') + return resp + + for res_regex, res_options in resources: + if try_match(request.path, res_regex): + LOG.debug("Request to '%s' matches CORS resource '%s'. Using options: %s", + request.path, get_regexp_pattern(res_regex), res_options) + set_cors_headers(resp, res_options) + break + else: + LOG.debug('No CORS rule matches') + return resp + return cors_after_request diff --git a/libs/flask_cors/version.py b/libs/flask_cors/version.py new file mode 100644 index 000000000..8a7ae5d4a --- /dev/null +++ b/libs/flask_cors/version.py @@ -0,0 +1 @@ +__version__ = '3.0.8' diff --git a/views/wizard_subtitles.html b/views/wizard_subtitles.html index f23247ef2..ab88efcd9 100644 --- a/views/wizard_subtitles.html +++ b/views/wizard_subtitles.html @@ -1,433 +1,449 @@ -
Subtitles options
-
-
+
Subtitles options
+
+
-
-
- -
-
- -
+
+
+ +
+
+ +
- -
- -
-
-
- -
-
-
-
- -
-
-
- - -
- -
-
- -
-
-
- - -
-
- -
+ +
+
+
+
+ +
+
+
+
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+
+ +
+ +
+
{% include('providers.html') %} -
Subtitles languages
-
-
-
-
- -
-
-
- - -
-
-
- -
-
-
-
We don't recommend enabling this option unless absolutely required (ie: media player not supporting language code in subtitles filename). Results may vary.
-
-
-
- -
-
- -
-
-
- -
-
-
+
Subtitles languages
+
+
+
+
+ +
+
+
+ + +
+
+
+ - -
Series default settings
-
-
-
-
- -
-
-
-
- - -
-
-
- -
- -
-
- -
-
-
- -
-
-
- -
-
- -
-
-
-
- - -
-
-
-
- -
-
- -
-
-
- -
-
-
+
+
+
+
We don't recommend enabling this option unless absolutely required (ie: + media player not supporting language code in subtitles filename). Results may vary.
+
+
-
Movie Default Settings
-
-
-
-
- -
-
-
-
- - -
-
-
- -
+
+
+ +
+
+
+ +
+
+
+
+
-
-
- -
-
-
- -
-
-
- -
-
- -
-
-
-
- - -
-
-
-
- -
-
- -
-
-
- -
-
-
+
Series default settings
+
+
+
+
+ +
+
+
+
+ +
+
+ +
- + if ($("#settings_single_language").checkbox('is checked')) { + $("#settings_serie_default_languages").parent().removeClass('multiple'); + $("#settings_serie_default_languages").removeAttr('multiple'); + $("#settings_movie_default_languages").parent().removeClass('multiple'); + $("#settings_movie_default_languages").removeAttr('multiple'); + } else { + $("#settings_serie_default_languages").parent().addClass('multiple'); + $("#settings_serie_default_languages").attr('multiple'); + $("#settings_movie_default_languages").parent().addClass('multiple'); + $("#settings_movie_default_languages").attr('multiple'); + } + + $("#settings_single_language").on('change', function () { + if ($("#settings_single_language").checkbox('is checked')) { + $("#settings_serie_default_languages").dropdown('clear'); + $("#settings_movie_default_languages").dropdown('clear'); + $("#settings_serie_default_languages").prepend(""); + $("#settings_movie_default_languages").prepend(""); + $("#settings_serie_default_languages").parent().removeClass('multiple'); + $("#settings_serie_default_languages").removeAttr('multiple'); + $("#settings_movie_default_languages").parent().removeClass('multiple'); + $("#settings_movie_default_languages").removeAttr('multiple'); + } else { + $("#settings_serie_default_languages").dropdown('clear'); + $("#settings_movie_default_languages").dropdown('clear'); + $("#settings_serie_default_languages option[value='None']").remove(); + $("#settings_movie_default_languages option[value='None']").remove(); + $("#settings_serie_default_languages").parent().addClass('multiple'); + $("#settings_serie_default_languages").attr('multiple'); + $("#settings_movie_default_languages").parent().addClass('multiple'); + $("#settings_movie_default_languages").attr('multiple'); + } + }); + + $('#settings_languages').dropdown('clear'); + $('#settings_languages').dropdown('set selected', {{ enabled_languages|safe }}); + $('#settings_subfolder').dropdown('clear'); + $('#settings_subfolder').dropdown('set selected', '{{ settings.general.subfolder }}'); + + {% if settings.general.serie_default_language != 'None' %} + $('#settings_serie_default_languages').dropdown('set selected', {{ settings.general.serie_default_language|safe }}); + {% endif %} + {% if settings.general.movie_default_language != 'None' %} + $('#settings_movie_default_languages').dropdown('set selected', {{ settings.general.movie_default_language|safe }}); + {% endif %} + + $('#settings_serie_default_forced').dropdown('set selected', '{{ settings.general.serie_default_forced }}'); + $('#settings_movie_default_forced').dropdown('set selected', '{{ settings.general.movie_default_forced }}'); + + $('#settings_languages').dropdown('setting', 'onChange', function () { + $('.form').form('validate field', 'settings_subliminal_languages'); + }); + + if (($('#settings_subfolder').val() !== "relative") && ($('#settings_subfolder').val() !== "absolute")) { + $('.subfolder').hide(); + } + + $('#settings_subfolder').dropdown('setting', 'onChange', function () { + if (($('#settings_subfolder').val() !== "relative") && ($('#settings_subfolder').val() !== "absolute")) { + $('.subfolder').hide(); + } else { + $('.subfolder').show(); + } + }); +