summaryrefslogtreecommitdiffhomepage
path: root/libs/apprise/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'libs/apprise/utils.py')
-rw-r--r--libs/apprise/utils.py85
1 files changed, 54 insertions, 31 deletions
diff --git a/libs/apprise/utils.py b/libs/apprise/utils.py
index 334711de9..561a5a232 100644
--- a/libs/apprise/utils.py
+++ b/libs/apprise/utils.py
@@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
+# BSD 3-Clause License
#
-# Copyright (C) 2019 Chris Caron <[email protected]>
-# All rights reserved.
+# Apprise - Push Notification Library.
+# Copyright (c) 2023, Chris Caron <[email protected]>
#
-# This code is licensed under the MIT License.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files(the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions :
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
+# 3. Neither the name of the copyright holder nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
import re
import sys
@@ -47,10 +54,6 @@ def import_module(path, name):
"""
Load our module based on path
"""
- # if path.endswith('test_module_detection0/a/hook.py'):
- # import pdb
- # pdb.set_trace()
-
spec = importlib.util.spec_from_file_location(name, path)
try:
module = importlib.util.module_from_spec(spec)
@@ -60,7 +63,13 @@ def import_module(path, name):
except Exception as e:
# module isn't loadable
- del sys.modules[name]
+ try:
+ del sys.modules[name]
+
+ except KeyError:
+ # nothing to clean up
+ pass
+
module = None
logger.debug(
@@ -153,10 +162,10 @@ URL_DETAILS_RE = re.compile(
GET_EMAIL_RE = re.compile(
- r'(([\s"\']+)?(?P<name>[^:<"\']+)?[:<\s"\']+)?'
+ r'(([\s"\']+)?(?P<name>[^:<\'"]+)?[:<\s\'"]+)?'
r'(?P<full_email>((?P<label>[^+]+)\+)?'
- r'(?P<email>(?P<userid>[a-z0-9$%=_~-]+'
- r'(?:\.[a-z0-9$%+=_~-]+)'
+ r'(?P<email>(?P<userid>[a-z0-9_!#$%&*/=?%`{|}~^-]+'
+ r'(?:\.[a-z0-9_!#$%&\'*/=?%`{|}~^-]+)'
r'*)@(?P<domain>('
r'(?:[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?\.)+'
r'[a-z0-9](?:[a-z0-9_-]*[a-z0-9]))|'
@@ -188,7 +197,7 @@ URL_DETECTION_RE = re.compile(
EMAIL_DETECTION_RE = re.compile(
r'[\s,]*([^@]+@.*?)(?=$|[\s,]+'
- + r'(?:[^:<]+?[:<\s]+?)?'
+ r'(?:[^:<]+?[:<\s]+?)?'
r'[^@\s,]+@[^\s,]+)',
re.IGNORECASE)
@@ -197,6 +206,9 @@ UUID4_RE = re.compile(
r'[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}',
re.IGNORECASE)
+# Validate if we're a loadable Python file or not
+VALID_PYTHON_FILE_RE = re.compile(r'.+\.py(o|c)?$', re.IGNORECASE)
+
# validate_regex() utilizes this mapping to track and re-use pre-complied
# regular expressions
REGEX_VALIDATE_LOOKUP = {}
@@ -519,7 +531,7 @@ def tidy_path(path):
return path
-def parse_qsd(qs, simple=False):
+def parse_qsd(qs, simple=False, plus_to_space=False):
"""
Query String Dictionary Builder
@@ -541,6 +553,11 @@ def parse_qsd(qs, simple=False):
if simple is set to true, then a ONE dictionary is returned and is not
sub-parsed for additional elements
+
+ plus_to_space will cause all `+` references to become a space as
+ per normal URL Encoded defininition. Normal URL parsing applies
+ this, but `+` is very actively used character with passwords,
+ api keys, tokens, etc. So Apprise does not do this by default.
"""
# Our return result set:
@@ -575,7 +592,7 @@ def parse_qsd(qs, simple=False):
key = unquote(key)
key = '' if not key else key
- val = nv[1].replace('+', ' ')
+ val = nv[1].replace('+', ' ') if plus_to_space else nv[1]
val = unquote(val)
val = '' if not val else val.strip()
@@ -609,7 +626,7 @@ def parse_qsd(qs, simple=False):
def parse_url(url, default_schema='http', verify_host=True, strict_port=False,
- simple=False):
+ simple=False, plus_to_space=False):
"""A function that greatly simplifies the parsing of a url
specified by the end user.
@@ -722,7 +739,8 @@ def parse_url(url, default_schema='http', verify_host=True, strict_port=False,
# Parse Query Arugments ?val=key&key=val
# while ensuring that all keys are lowercase
if qsdata:
- result.update(parse_qsd(qsdata, simple=simple))
+ result.update(parse_qsd(
+ qsdata, simple=simple, plus_to_space=plus_to_space))
# Now do a proper extraction of data; http:// is just substitued in place
# to allow urlparse() to function as expected, we'll swap this back to the
@@ -1556,6 +1574,11 @@ def module_detection(paths, cache=True):
# Since our plugin name can conflict (as a module) with another
# we want to generate random strings to avoid steping on
# another's namespace
+ if not (path and VALID_PYTHON_FILE_RE.match(path)):
+ # Ignore file/module type
+ logger.trace('Plugin Scan: Skipping %s', path)
+ return None
+
module_name = hashlib.sha1(path.encode('utf-8')).hexdigest()
module_pyname = "{prefix}.{name}".format(
prefix='apprise.custom.module', name=module_name)