From ad6316bda0cc1dc77a41acbe4ea8862d0fa8b4a3 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sat, 8 Aug 2020 09:08:49 -0400 Subject: [PATCH] Improved URL parsing; introducing IPV6 support (#269) --- apprise/attachment/AttachHTTP.py | 2 +- apprise/config/ConfigHTTP.py | 2 +- apprise/plugins/NotifyBoxcar.py | 1 - apprise/plugins/NotifyClickSend.py | 2 +- apprise/plugins/NotifyD7Networks.py | 3 +- apprise/plugins/NotifyDBus.py | 24 +--- apprise/plugins/NotifyDiscord.py | 5 +- apprise/plugins/NotifyEmail.py | 6 +- apprise/plugins/NotifyEmby.py | 19 ++-- apprise/plugins/NotifyEnigma2.py | 6 +- apprise/plugins/NotifyFaast.py | 13 ++- apprise/plugins/NotifyFlock.py | 5 +- apprise/plugins/NotifyGitter.py | 5 +- apprise/plugins/NotifyGnome.py | 18 +-- apprise/plugins/NotifyGotify.py | 5 +- apprise/plugins/NotifyGrowl.py | 6 +- apprise/plugins/NotifyIFTTT.py | 5 +- apprise/plugins/NotifyJSON.py | 6 +- apprise/plugins/NotifyJoin.py | 5 +- apprise/plugins/NotifyKavenegar.py | 3 +- apprise/plugins/NotifyKumulos.py | 5 +- apprise/plugins/NotifyLametric.py | 11 +- apprise/plugins/NotifyMSG91.py | 5 +- apprise/plugins/NotifyMSTeams.py | 3 +- apprise/plugins/NotifyMacOSX.py | 16 +-- apprise/plugins/NotifyMailgun.py | 5 +- apprise/plugins/NotifyMatrix.py | 2 +- apprise/plugins/NotifyMatterMost.py | 7 +- apprise/plugins/NotifyMessageBird.py | 7 +- apprise/plugins/NotifyNexmo.py | 3 +- apprise/plugins/NotifyNextcloud.py | 6 +- apprise/plugins/NotifyNotica.py | 2 +- apprise/plugins/NotifyNotifico.py | 4 +- apprise/plugins/NotifyOffice365.py | 2 +- apprise/plugins/NotifyPopcornNotify.py | 5 +- apprise/plugins/NotifyProwl.py | 5 +- apprise/plugins/NotifyPushBullet.py | 5 +- apprise/plugins/NotifyPushSafer.py | 4 +- apprise/plugins/NotifyPushed.py | 5 +- apprise/plugins/NotifyPushjet.py | 6 +- apprise/plugins/NotifyPushover.py | 5 +- apprise/plugins/NotifyRocketChat.py | 6 +- apprise/plugins/NotifyRyver.py | 5 +- apprise/plugins/NotifySNS.py | 5 +- apprise/plugins/NotifySendGrid.py | 5 +- apprise/plugins/NotifySimplePush.py | 4 +- apprise/plugins/NotifySinch.py | 2 +- apprise/plugins/NotifySlack.py | 2 +- apprise/plugins/NotifySpontit.py | 2 +- apprise/plugins/NotifySyslog.py | 3 +- apprise/plugins/NotifyTechulusPush.py | 3 +- apprise/plugins/NotifyTelegram.py | 15 +-- apprise/plugins/NotifyTwilio.py | 2 +- apprise/plugins/NotifyTwist.py | 3 +- apprise/plugins/NotifyTwitter.py | 3 +- apprise/plugins/NotifyWebexTeams.py | 3 +- apprise/plugins/NotifyWindows.py | 18 +-- apprise/plugins/NotifyXBMC.py | 13 +-- apprise/plugins/NotifyXML.py | 6 +- apprise/plugins/NotifyXMPP/__init__.py | 6 +- apprise/plugins/NotifyZulip.py | 5 +- apprise/utils.py | 152 +++++++++++++++---------- test/test_glib_plugin.py | 3 - test/test_rest_plugins.py | 145 ++++++++++++----------- test/test_utils.py | 110 +++++++++++++----- 65 files changed, 395 insertions(+), 380 deletions(-) diff --git a/apprise/attachment/AttachHTTP.py b/apprise/attachment/AttachHTTP.py index 7340cf48..d5396cf8 100644 --- a/apprise/attachment/AttachHTTP.py +++ b/apprise/attachment/AttachHTTP.py @@ -310,7 +310,7 @@ class AttachHTTP(AttachBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = AttachBase.parse_url(url) diff --git a/apprise/config/ConfigHTTP.py b/apprise/config/ConfigHTTP.py index 4ce4f7ac..08714961 100644 --- a/apprise/config/ConfigHTTP.py +++ b/apprise/config/ConfigHTTP.py @@ -252,7 +252,7 @@ class ConfigHTTP(ConfigBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = ConfigBase.parse_url(url) diff --git a/apprise/plugins/NotifyBoxcar.py b/apprise/plugins/NotifyBoxcar.py index ef6e1bd2..04b43b19 100644 --- a/apprise/plugins/NotifyBoxcar.py +++ b/apprise/plugins/NotifyBoxcar.py @@ -346,7 +346,6 @@ class NotifyBoxcar(NotifyBase): """ results = NotifyBase.parse_url(url, verify_host=False) - if not results: # We're done early return None diff --git a/apprise/plugins/NotifyClickSend.py b/apprise/plugins/NotifyClickSend.py index 8949b405..a7d89c18 100644 --- a/apprise/plugins/NotifyClickSend.py +++ b/apprise/plugins/NotifyClickSend.py @@ -299,7 +299,7 @@ class NotifyClickSend(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) diff --git a/apprise/plugins/NotifyD7Networks.py b/apprise/plugins/NotifyD7Networks.py index f9a7a0c5..f04082c6 100644 --- a/apprise/plugins/NotifyD7Networks.py +++ b/apprise/plugins/NotifyD7Networks.py @@ -422,11 +422,10 @@ class NotifyD7Networks(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyDBus.py b/apprise/plugins/NotifyDBus.py index 2dbb033b..ca501bf9 100644 --- a/apprise/plugins/NotifyDBus.py +++ b/apprise/plugins/NotifyDBus.py @@ -29,7 +29,6 @@ from __future__ import print_function from .NotifyBase import NotifyBase from ..common import NotifyImageSize from ..common import NotifyType -from ..utils import GET_SCHEMA_RE from ..utils import parse_bool from ..AppriseLocale import gettext_lazy as _ @@ -141,7 +140,6 @@ class NotifyDBus(NotifyBase): # object if we were to reference, we wouldn't be backwards compatible with # Python v2. So converting the result set back into a list makes us # compatible - protocol = list(MAINLOOP_MAP.keys()) # A URL that takes you to the setup/help of the specific protocol @@ -153,7 +151,7 @@ class NotifyDBus(NotifyBase): # Allows the user to specify the NotifyImageSize object image_size = NotifyImageSize.XY_128 - # The number of seconds to keep the message present for + # The number of milliseconds to keep the message present for message_timeout_ms = 13000 # Limit results to just the first 10 line otherwise there is just to much @@ -171,7 +169,7 @@ class NotifyDBus(NotifyBase): # Define object templates templates = ( - '{schema}://_/', + '{schema}://', ) # Define our template arguments @@ -386,24 +384,8 @@ class NotifyDBus(NotifyBase): is in place. """ - schema = GET_SCHEMA_RE.match(url) - if schema is None: - # Content is simply not parseable - return None - results = NotifyBase.parse_url(url) - if not results: - results = { - 'schema': schema.group('schema').lower(), - 'user': None, - 'password': None, - 'port': None, - 'host': '_', - 'fullpath': None, - 'path': None, - 'url': url, - 'qsd': {}, - } + results = NotifyBase.parse_url(url, verify_host=False) # Include images with our message results['include_image'] = \ diff --git a/apprise/plugins/NotifyDiscord.py b/apprise/plugins/NotifyDiscord.py index 8c6fd0a5..66aa429b 100644 --- a/apprise/plugins/NotifyDiscord.py +++ b/apprise/plugins/NotifyDiscord.py @@ -429,14 +429,13 @@ class NotifyDiscord(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. Syntax: discord://webhook_id/webhook_token """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyEmail.py b/apprise/plugins/NotifyEmail.py index c13ad2ea..b22aa708 100644 --- a/apprise/plugins/NotifyEmail.py +++ b/apprise/plugins/NotifyEmail.py @@ -737,7 +737,8 @@ class NotifyEmail(NotifyBase): return '{schema}://{auth}{hostname}{port}/{targets}?{params}'.format( schema=self.secure_protocol if self.secure else self.protocol, auth=auth, - hostname=NotifyEmail.quote(self.host, safe=''), + # never encode hostname since we're expecting it to be a valid one + hostname=self.host, port='' if self.port is None or self.port == default_port else ':{}'.format(self.port), targets='' if not has_targets else '/'.join( @@ -749,11 +750,10 @@ class NotifyEmail(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyEmby.py b/apprise/plugins/NotifyEmby.py index 1ad09da3..bea4d2b5 100644 --- a/apprise/plugins/NotifyEmby.py +++ b/apprise/plugins/NotifyEmby.py @@ -61,9 +61,6 @@ class NotifyEmby(NotifyBase): # A URL that takes you to the setup/help of the specific protocol setup_url = 'https://github.com/caronc/apprise/wiki/Notify_emby' - # Emby uses the http protocol with JSON requests - emby_default_port = 8096 - # By default Emby requires you to provide it a device id # The following was just a random uuid4 generated one. There # is no real reason to change this, but hey; that's what open @@ -94,6 +91,7 @@ class NotifyEmby(NotifyBase): 'type': 'int', 'min': 1, 'max': 65535, + 'default': 8096 }, 'user': { 'name': _('Username'), @@ -137,6 +135,10 @@ class NotifyEmby(NotifyBase): # or a modal type box (requires an Okay acknowledgement) self.modal = modal + if not self.port: + # Assign default port if one isn't otherwise specified: + self.port = self.template_tokens['port']['default'] + if not self.user: # User was not specified msg = 'No Emby username was specified.' @@ -620,8 +622,9 @@ class NotifyEmby(NotifyBase): return '{schema}://{auth}{hostname}{port}/?{params}'.format( schema=self.secure_protocol if self.secure else self.protocol, auth=auth, - hostname=NotifyEmby.quote(self.host, safe=''), - port='' if self.port is None or self.port == self.emby_default_port + hostname=self.host, + port='' if self.port is None + or self.port == self.template_tokens['port']['default'] else ':{}'.format(self.port), params=NotifyEmby.urlencode(params), ) @@ -659,7 +662,7 @@ class NotifyEmby(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url) @@ -667,10 +670,6 @@ class NotifyEmby(NotifyBase): # We're done early return results - # Assign Default Emby Port - if not results['port']: - results['port'] = NotifyEmby.emby_default_port - # Modal type popup (default False) results['modal'] = parse_bool(results['qsd'].get('modal', False)) diff --git a/apprise/plugins/NotifyEnigma2.py b/apprise/plugins/NotifyEnigma2.py index 1749eb98..1a8e97fc 100644 --- a/apprise/plugins/NotifyEnigma2.py +++ b/apprise/plugins/NotifyEnigma2.py @@ -213,7 +213,8 @@ class NotifyEnigma2(NotifyBase): return '{schema}://{auth}{hostname}{port}{fullpath}?{params}'.format( schema=self.secure_protocol if self.secure else self.protocol, auth=auth, - hostname=NotifyEnigma2.quote(self.host, safe=''), + # never encode hostname since we're expecting it to be a valid one + hostname=self.host, port='' if self.port is None or self.port == default_port else ':{}'.format(self.port), fullpath=NotifyEnigma2.quote(self.fullpath, safe='/'), @@ -327,11 +328,10 @@ class NotifyEnigma2(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyFaast.py b/apprise/plugins/NotifyFaast.py index a56476fa..d34b4800 100644 --- a/apprise/plugins/NotifyFaast.py +++ b/apprise/plugins/NotifyFaast.py @@ -29,6 +29,7 @@ from ..common import NotifyImageSize from ..common import NotifyType from ..utils import parse_bool from ..AppriseLocale import gettext_lazy as _ +from ..utils import validate_regex class NotifyFaast(NotifyBase): @@ -86,7 +87,12 @@ class NotifyFaast(NotifyBase): super(NotifyFaast, self).__init__(**kwargs) # Store the Authentication Token - self.authtoken = authtoken + self.authtoken = validate_regex(authtoken) + if not self.authtoken: + msg = 'An invalid Faast Authentication Token ' \ + '({}) was specified.'.format(authtoken) + self.logger.warning(msg) + raise TypeError(msg) # Associate an image with our post self.include_image = include_image @@ -187,11 +193,10 @@ class NotifyFaast(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyFlock.py b/apprise/plugins/NotifyFlock.py index 0c920983..2c68cc1c 100644 --- a/apprise/plugins/NotifyFlock.py +++ b/apprise/plugins/NotifyFlock.py @@ -331,10 +331,9 @@ class NotifyFlock(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyGitter.py b/apprise/plugins/NotifyGitter.py index 3702c241..d94d4146 100644 --- a/apprise/plugins/NotifyGitter.py +++ b/apprise/plugins/NotifyGitter.py @@ -386,11 +386,10 @@ class NotifyGitter(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyGnome.py b/apprise/plugins/NotifyGnome.py index e5058d2c..4f5e5860 100644 --- a/apprise/plugins/NotifyGnome.py +++ b/apprise/plugins/NotifyGnome.py @@ -113,7 +113,7 @@ class NotifyGnome(NotifyBase): # Define object templates templates = ( - '{schema}://_/', + '{schema}://', ) # Define our template arguments @@ -224,7 +224,7 @@ class NotifyGnome(NotifyBase): # Extend our parameters params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) - return '{schema}://_/?{params}'.format( + return '{schema}://?{params}'.format( schema=self.protocol, params=NotifyGnome.urlencode(params), ) @@ -238,19 +238,7 @@ class NotifyGnome(NotifyBase): """ - results = NotifyBase.parse_url(url) - if not results: - results = { - 'schema': NotifyGnome.protocol, - 'user': None, - 'password': None, - 'port': None, - 'host': '_', - 'fullpath': None, - 'path': None, - 'url': url, - 'qsd': {}, - } + results = NotifyBase.parse_url(url, verify_host=False) # Include images with our message results['include_image'] = \ diff --git a/apprise/plugins/NotifyGotify.py b/apprise/plugins/NotifyGotify.py index 9bf92a66..a04a6952 100644 --- a/apprise/plugins/NotifyGotify.py +++ b/apprise/plugins/NotifyGotify.py @@ -255,7 +255,8 @@ class NotifyGotify(NotifyBase): return '{schema}://{hostname}{port}{fullpath}{token}/?{params}'.format( schema=self.secure_protocol if self.secure else self.protocol, - hostname=NotifyGotify.quote(self.host, safe=''), + # never encode hostname since we're expecting it to be a valid one + hostname=self.host, port='' if self.port is None or self.port == default_port else ':{}'.format(self.port), fullpath=NotifyGotify.quote(self.fullpath, safe='/'), @@ -267,7 +268,7 @@ class NotifyGotify(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url) diff --git a/apprise/plugins/NotifyGrowl.py b/apprise/plugins/NotifyGrowl.py index a74c56a4..e9df69dc 100644 --- a/apprise/plugins/NotifyGrowl.py +++ b/apprise/plugins/NotifyGrowl.py @@ -359,7 +359,8 @@ class NotifyGrowl(NotifyBase): return '{schema}://{auth}{hostname}{port}/?{params}'.format( schema=self.secure_protocol if self.secure else self.protocol, auth=auth, - hostname=NotifyGrowl.quote(self.host, safe=''), + # never encode hostname since we're expecting it to be a valid one + hostname=self.host, port='' if self.port is None or self.port == self.default_port else ':{}'.format(self.port), params=NotifyGrowl.urlencode(params), @@ -369,11 +370,10 @@ class NotifyGrowl(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyIFTTT.py b/apprise/plugins/NotifyIFTTT.py index b5f50c68..e6b40acd 100644 --- a/apprise/plugins/NotifyIFTTT.py +++ b/apprise/plugins/NotifyIFTTT.py @@ -310,11 +310,10 @@ class NotifyIFTTT(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyJSON.py b/apprise/plugins/NotifyJSON.py index 114c8c92..d8a55ac8 100644 --- a/apprise/plugins/NotifyJSON.py +++ b/apprise/plugins/NotifyJSON.py @@ -152,7 +152,8 @@ class NotifyJSON(NotifyBase): return '{schema}://{auth}{hostname}{port}{fullpath}/?{params}'.format( schema=self.secure_protocol if self.secure else self.protocol, auth=auth, - hostname=NotifyJSON.quote(self.host, safe=''), + # never encode hostname since we're expecting it to be a valid one + hostname=self.host, port='' if self.port is None or self.port == default_port else ':{}'.format(self.port), fullpath=NotifyJSON.quote(self.fullpath, safe='/'), @@ -248,11 +249,10 @@ class NotifyJSON(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyJoin.py b/apprise/plugins/NotifyJoin.py index 33eda192..d5e3f947 100644 --- a/apprise/plugins/NotifyJoin.py +++ b/apprise/plugins/NotifyJoin.py @@ -354,11 +354,10 @@ class NotifyJoin(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyKavenegar.py b/apprise/plugins/NotifyKavenegar.py index 229d50b9..cd572636 100644 --- a/apprise/plugins/NotifyKavenegar.py +++ b/apprise/plugins/NotifyKavenegar.py @@ -341,11 +341,10 @@ class NotifyKavenegar(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyKumulos.py b/apprise/plugins/NotifyKumulos.py index 51492e36..8506aef3 100644 --- a/apprise/plugins/NotifyKumulos.py +++ b/apprise/plugins/NotifyKumulos.py @@ -214,11 +214,10 @@ class NotifyKumulos(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyLametric.py b/apprise/plugins/NotifyLametric.py index fd271f32..a17eeb60 100644 --- a/apprise/plugins/NotifyLametric.py +++ b/apprise/plugins/NotifyLametric.py @@ -298,9 +298,6 @@ class NotifyLametric(NotifyBase): # URL used for local notifications directly to the device device_notify_url = '{schema}://{host}{port}/api/v2/device/notifications' - # LaMetric Default port - default_device_port = 8080 - # The Device User ID default_device_user = 'dev' @@ -350,6 +347,7 @@ class NotifyLametric(NotifyBase): 'type': 'int', 'min': 1, 'max': 65535, + 'default': 8080, }, 'user': { 'name': _('Username'), @@ -609,7 +607,8 @@ class NotifyLametric(NotifyBase): schema="https" if self.secure else "http", host=self.host, port=':{}'.format( - self.port if self.port else self.default_device_port)) + self.port if self.port + else self.template_tokens['port']['default'])) # Return request parameters return (notify_url, auth, payload) @@ -746,7 +745,7 @@ class NotifyLametric(NotifyBase): # never encode hostname since we're expecting it to be a valid one hostname=self.host, port='' if self.port is None - or self.port == self.default_device_port + or self.port == self.template_tokens['port']['default'] else ':{}'.format(self.port), params=NotifyLametric.urlencode(params), ) @@ -755,7 +754,7 @@ class NotifyLametric(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ diff --git a/apprise/plugins/NotifyMSG91.py b/apprise/plugins/NotifyMSG91.py index 5ed37ea4..68176fb9 100644 --- a/apprise/plugins/NotifyMSG91.py +++ b/apprise/plugins/NotifyMSG91.py @@ -339,12 +339,11 @@ class NotifyMSG91(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyMSTeams.py b/apprise/plugins/NotifyMSTeams.py index ebf5617a..b12c5e45 100644 --- a/apprise/plugins/NotifyMSTeams.py +++ b/apprise/plugins/NotifyMSTeams.py @@ -299,11 +299,10 @@ class NotifyMSTeams(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyMacOSX.py b/apprise/plugins/NotifyMacOSX.py index cc6020ca..d1160c37 100644 --- a/apprise/plugins/NotifyMacOSX.py +++ b/apprise/plugins/NotifyMacOSX.py @@ -69,7 +69,7 @@ class NotifyMacOSX(NotifyBase): # Define object templates templates = ( - '{schema}://_/', + '{schema}://', ) # Define our template arguments @@ -206,19 +206,7 @@ class NotifyMacOSX(NotifyBase): """ - results = NotifyBase.parse_url(url) - if not results: - results = { - 'schema': NotifyMacOSX.protocol, - 'user': None, - 'password': None, - 'port': None, - 'host': '_', - 'fullpath': None, - 'path': None, - 'url': url, - 'qsd': {}, - } + results = NotifyBase.parse_url(url, verify_host=False) # Include images with our message results['include_image'] = \ diff --git a/apprise/plugins/NotifyMailgun.py b/apprise/plugins/NotifyMailgun.py index 85fd1b66..e876a5bd 100644 --- a/apprise/plugins/NotifyMailgun.py +++ b/apprise/plugins/NotifyMailgun.py @@ -340,11 +340,10 @@ class NotifyMailgun(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyMatrix.py b/apprise/plugins/NotifyMatrix.py index 82f8e6a9..78414691 100644 --- a/apprise/plugins/NotifyMatrix.py +++ b/apprise/plugins/NotifyMatrix.py @@ -1050,7 +1050,7 @@ class NotifyMatrix(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) diff --git a/apprise/plugins/NotifyMatterMost.py b/apprise/plugins/NotifyMatterMost.py index fda19d5f..edd8202d 100644 --- a/apprise/plugins/NotifyMatterMost.py +++ b/apprise/plugins/NotifyMatterMost.py @@ -307,7 +307,9 @@ class NotifyMatterMost(NotifyBase): '/?{params}'.format( schema=default_schema, botname=botname, - hostname=NotifyMatterMost.quote(self.host, safe=''), + # never encode hostname since we're expecting it to be a valid + # one + hostname=self.host, port='' if not self.port or self.port == default_port else ':{}'.format(self.port), fullpath='/' if not self.fullpath else '{}/'.format( @@ -320,11 +322,10 @@ class NotifyMatterMost(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyMessageBird.py b/apprise/plugins/NotifyMessageBird.py index 364d46cc..1032f49b 100644 --- a/apprise/plugins/NotifyMessageBird.py +++ b/apprise/plugins/NotifyMessageBird.py @@ -329,12 +329,11 @@ class NotifyMessageBird(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results @@ -349,7 +348,7 @@ class NotifyMessageBird(NotifyBase): except IndexError: # No path specified... this URL is potentially un-parseable; we can # hope for a from= entry - pass + results['source'] = None # The hostname is our authentication key results['apikey'] = NotifyMessageBird.unquote(results['host']) diff --git a/apprise/plugins/NotifyNexmo.py b/apprise/plugins/NotifyNexmo.py index 4b98cb84..05c9f7fc 100644 --- a/apprise/plugins/NotifyNexmo.py +++ b/apprise/plugins/NotifyNexmo.py @@ -347,11 +347,10 @@ class NotifyNexmo(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyNextcloud.py b/apprise/plugins/NotifyNextcloud.py index a69c4c05..240ed0aa 100644 --- a/apprise/plugins/NotifyNextcloud.py +++ b/apprise/plugins/NotifyNextcloud.py @@ -253,7 +253,9 @@ class NotifyNextcloud(NotifyBase): schema=self.secure_protocol if self.secure else self.protocol, auth=auth, - hostname=NotifyNextcloud.quote(self.host, safe=''), + # never encode hostname since we're expecting it to be a + # valid one + hostname=self.host, port='' if self.port is None or self.port == default_port else ':{}'.format(self.port), targets='/'.join([NotifyNextcloud.quote(x) @@ -265,7 +267,7 @@ class NotifyNextcloud(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ diff --git a/apprise/plugins/NotifyNotica.py b/apprise/plugins/NotifyNotica.py index b367b516..3dcc0172 100644 --- a/apprise/plugins/NotifyNotica.py +++ b/apprise/plugins/NotifyNotica.py @@ -331,7 +331,7 @@ class NotifyNotica(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) diff --git a/apprise/plugins/NotifyNotifico.py b/apprise/plugins/NotifyNotifico.py index eaf1c769..b0970e19 100644 --- a/apprise/plugins/NotifyNotifico.py +++ b/apprise/plugins/NotifyNotifico.py @@ -325,11 +325,11 @@ class NotifyNotifico(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyOffice365.py b/apprise/plugins/NotifyOffice365.py index d9e326a6..b7cef03a 100644 --- a/apprise/plugins/NotifyOffice365.py +++ b/apprise/plugins/NotifyOffice365.py @@ -472,7 +472,7 @@ class NotifyOffice365(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ diff --git a/apprise/plugins/NotifyPopcornNotify.py b/apprise/plugins/NotifyPopcornNotify.py index 8f394f5d..164078be 100644 --- a/apprise/plugins/NotifyPopcornNotify.py +++ b/apprise/plugins/NotifyPopcornNotify.py @@ -274,12 +274,11 @@ class NotifyPopcornNotify(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyProwl.py b/apprise/plugins/NotifyProwl.py index 6d531a0e..8341064d 100644 --- a/apprise/plugins/NotifyProwl.py +++ b/apprise/plugins/NotifyProwl.py @@ -257,11 +257,10 @@ class NotifyProwl(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyPushBullet.py b/apprise/plugins/NotifyPushBullet.py index 78b04df4..78e9168c 100644 --- a/apprise/plugins/NotifyPushBullet.py +++ b/apprise/plugins/NotifyPushBullet.py @@ -395,11 +395,10 @@ class NotifyPushBullet(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyPushSafer.py b/apprise/plugins/NotifyPushSafer.py index 65de3666..2d74dc37 100644 --- a/apprise/plugins/NotifyPushSafer.py +++ b/apprise/plugins/NotifyPushSafer.py @@ -793,10 +793,10 @@ class NotifyPushSafer(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyPushed.py b/apprise/plugins/NotifyPushed.py index b4f53e03..c6dfe6ad 100644 --- a/apprise/plugins/NotifyPushed.py +++ b/apprise/plugins/NotifyPushed.py @@ -326,11 +326,10 @@ class NotifyPushed(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyPushjet.py b/apprise/plugins/NotifyPushjet.py index dc1ce232..53cc5ba3 100644 --- a/apprise/plugins/NotifyPushjet.py +++ b/apprise/plugins/NotifyPushjet.py @@ -140,7 +140,8 @@ class NotifyPushjet(NotifyBase): return '{schema}://{auth}{hostname}{port}/{secret}/?{params}'.format( schema=self.secure_protocol if self.secure else self.protocol, auth=auth, - hostname=NotifyPushjet.quote(self.host, safe=''), + # never encode hostname since we're expecting it to be a valid one + hostname=self.host, port='' if self.port is None or self.port == default_port else ':{}'.format(self.port), secret=self.pprint( @@ -232,7 +233,7 @@ class NotifyPushjet(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. Syntax: pjet://hostname/secret_key @@ -252,7 +253,6 @@ class NotifyPushjet(NotifyBase): """ results = NotifyBase.parse_url(url) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyPushover.py b/apprise/plugins/NotifyPushover.py index 0ab03b72..e9fdb702 100644 --- a/apprise/plugins/NotifyPushover.py +++ b/apprise/plugins/NotifyPushover.py @@ -532,11 +532,10 @@ class NotifyPushover(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyRocketChat.py b/apprise/plugins/NotifyRocketChat.py index a9d8bf46..9beda256 100644 --- a/apprise/plugins/NotifyRocketChat.py +++ b/apprise/plugins/NotifyRocketChat.py @@ -313,7 +313,8 @@ class NotifyRocketChat(NotifyBase): return '{schema}://{auth}{hostname}{port}/{targets}/?{params}'.format( schema=self.secure_protocol if self.secure else self.protocol, auth=auth, - hostname=NotifyRocketChat.quote(self.host, safe=''), + # never encode hostname since we're expecting it to be a valid one + hostname=self.host, port='' if self.port is None or self.port == default_port else ':{}'.format(self.port), targets='/'.join( @@ -636,7 +637,7 @@ class NotifyRocketChat(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ @@ -668,7 +669,6 @@ class NotifyRocketChat(NotifyBase): ) results = NotifyBase.parse_url(url) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyRyver.py b/apprise/plugins/NotifyRyver.py index 6e90bb4d..dfc0b257 100644 --- a/apprise/plugins/NotifyRyver.py +++ b/apprise/plugins/NotifyRyver.py @@ -302,12 +302,11 @@ class NotifyRyver(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifySNS.py b/apprise/plugins/NotifySNS.py index 6f0d1c4f..adbbdfbb 100644 --- a/apprise/plugins/NotifySNS.py +++ b/apprise/plugins/NotifySNS.py @@ -605,11 +605,10 @@ class NotifySNS(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifySendGrid.py b/apprise/plugins/NotifySendGrid.py index f505193d..20234295 100644 --- a/apprise/plugins/NotifySendGrid.py +++ b/apprise/plugins/NotifySendGrid.py @@ -272,7 +272,8 @@ class NotifySendGrid(NotifyBase): return '{schema}://{apikey}:{from_email}/{targets}?{params}'.format( schema=self.secure_protocol, apikey=self.pprint(self.apikey, privacy, safe=''), - from_email=self.quote(self.from_email, safe='@'), + # never encode email since it plays a huge role in our hostname + from_email=self.from_email, targets='' if not has_targets else '/'.join( [NotifySendGrid.quote(x, safe='') for x in self.targets]), params=NotifySendGrid.urlencode(params), @@ -401,7 +402,7 @@ class NotifySendGrid(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ diff --git a/apprise/plugins/NotifySimplePush.py b/apprise/plugins/NotifySimplePush.py index 53adfaa1..dd192e79 100644 --- a/apprise/plugins/NotifySimplePush.py +++ b/apprise/plugins/NotifySimplePush.py @@ -314,10 +314,10 @@ class NotifySimplePush(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifySinch.py b/apprise/plugins/NotifySinch.py index 88a90aff..c3cc3267 100644 --- a/apprise/plugins/NotifySinch.py +++ b/apprise/plugins/NotifySinch.py @@ -422,7 +422,7 @@ class NotifySinch(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) diff --git a/apprise/plugins/NotifySlack.py b/apprise/plugins/NotifySlack.py index 26f9ff92..3e024a64 100644 --- a/apprise/plugins/NotifySlack.py +++ b/apprise/plugins/NotifySlack.py @@ -692,7 +692,7 @@ class NotifySlack(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) diff --git a/apprise/plugins/NotifySpontit.py b/apprise/plugins/NotifySpontit.py index 8984f7c2..91388ea1 100644 --- a/apprise/plugins/NotifySpontit.py +++ b/apprise/plugins/NotifySpontit.py @@ -347,7 +347,7 @@ class NotifySpontit(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ diff --git a/apprise/plugins/NotifySyslog.py b/apprise/plugins/NotifySyslog.py index bbecbcd1..2457410e 100644 --- a/apprise/plugins/NotifySyslog.py +++ b/apprise/plugins/NotifySyslog.py @@ -254,11 +254,12 @@ class NotifySyslog(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) if not results: + # We're done early as we couldn't load the results return results # if specified; save hostname into facility diff --git a/apprise/plugins/NotifyTechulusPush.py b/apprise/plugins/NotifyTechulusPush.py index b92ec1d2..5dcb33e5 100644 --- a/apprise/plugins/NotifyTechulusPush.py +++ b/apprise/plugins/NotifyTechulusPush.py @@ -199,11 +199,10 @@ class NotifyTechulusPush(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyTelegram.py b/apprise/plugins/NotifyTelegram.py index 7823ac0e..aeddc366 100644 --- a/apprise/plugins/NotifyTelegram.py +++ b/apprise/plugins/NotifyTelegram.py @@ -688,7 +688,7 @@ class NotifyTelegram(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ # This is a dirty hack; but it's the only work around to tgram:// @@ -721,17 +721,14 @@ class NotifyTelegram(NotifyBase): tgram.group('protocol'), tgram.group('prefix'), tgram.group('btoken_a'), - tgram.group('remaining'))) + tgram.group('remaining')), verify_host=False) else: # Try again - results = NotifyBase.parse_url( - '%s%s/%s' % ( - tgram.group('protocol'), - tgram.group('btoken_a'), - tgram.group('remaining'), - ), - ) + results = NotifyBase.parse_url('%s%s/%s' % ( + tgram.group('protocol'), + tgram.group('btoken_a'), + tgram.group('remaining')), verify_host=False) # The first token is stored in the hostname bot_token_a = NotifyTelegram.unquote(results['host']) diff --git a/apprise/plugins/NotifyTwilio.py b/apprise/plugins/NotifyTwilio.py index 1ad778bf..4ab19713 100644 --- a/apprise/plugins/NotifyTwilio.py +++ b/apprise/plugins/NotifyTwilio.py @@ -385,7 +385,7 @@ class NotifyTwilio(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) diff --git a/apprise/plugins/NotifyTwist.py b/apprise/plugins/NotifyTwist.py index 8d7a1243..c7c19684 100644 --- a/apprise/plugins/NotifyTwist.py +++ b/apprise/plugins/NotifyTwist.py @@ -726,11 +726,10 @@ class NotifyTwist(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyTwitter.py b/apprise/plugins/NotifyTwitter.py index 7b23e6d2..e0135342 100644 --- a/apprise/plugins/NotifyTwitter.py +++ b/apprise/plugins/NotifyTwitter.py @@ -609,11 +609,10 @@ class NotifyTwitter(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyWebexTeams.py b/apprise/plugins/NotifyWebexTeams.py index b0c36c54..5e802133 100644 --- a/apprise/plugins/NotifyWebexTeams.py +++ b/apprise/plugins/NotifyWebexTeams.py @@ -222,11 +222,10 @@ class NotifyWebexTeams(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url, verify_host=False) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyWindows.py b/apprise/plugins/NotifyWindows.py index b3a20ed4..9c957f9d 100644 --- a/apprise/plugins/NotifyWindows.py +++ b/apprise/plugins/NotifyWindows.py @@ -91,7 +91,7 @@ class NotifyWindows(NotifyBase): # Define object templates templates = ( - '{schema}://_/', + '{schema}://', ) # Define our template arguments @@ -232,7 +232,7 @@ class NotifyWindows(NotifyBase): # Extend our parameters params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) - return '{schema}://_/?{params}'.format( + return '{schema}://?{params}'.format( schema=self.protocol, params=NotifyWindows.urlencode(params), ) @@ -246,19 +246,7 @@ class NotifyWindows(NotifyBase): """ - results = NotifyBase.parse_url(url) - if not results: - results = { - 'schema': NotifyWindows.protocol, - 'user': None, - 'password': None, - 'port': None, - 'host': '_', - 'fullpath': None, - 'path': None, - 'url': url, - 'qsd': {}, - } + results = NotifyBase.parse_url(url, verify_host=False) # Include images with our message results['include_image'] = \ diff --git a/apprise/plugins/NotifyXBMC.py b/apprise/plugins/NotifyXBMC.py index 5fcbc4e0..22f4219c 100644 --- a/apprise/plugins/NotifyXBMC.py +++ b/apprise/plugins/NotifyXBMC.py @@ -73,9 +73,6 @@ class NotifyXBMC(NotifyBase): # Allows the user to specify the NotifyImageSize object image_size = NotifyImageSize.XY_128 - # The number of seconds to display the popup for - default_popup_duration_sec = 12 - # XBMC default protocol version (v2) xbmc_remote_protocol = 2 @@ -137,8 +134,9 @@ class NotifyXBMC(NotifyBase): super(NotifyXBMC, self).__init__(**kwargs) # Number of seconds to display notification for - self.duration = self.default_popup_duration_sec \ - if not (isinstance(duration, int) and duration > 0) else duration + self.duration = self.template_args['duration']['default'] \ + if not (isinstance(duration, int) and + self.template_args['duration']['min'] > 0) else duration # Build our schema self.schema = 'https' if self.secure else 'http' @@ -335,7 +333,8 @@ class NotifyXBMC(NotifyBase): return '{schema}://{auth}{hostname}{port}/?{params}'.format( schema=default_schema, auth=auth, - hostname=NotifyXBMC.quote(self.host, safe=''), + # never encode hostname since we're expecting it to be a valid one + hostname=self.host, port='' if not self.port or self.port == default_port else ':{}'.format(self.port), params=NotifyXBMC.urlencode(params), @@ -345,7 +344,7 @@ class NotifyXBMC(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url) diff --git a/apprise/plugins/NotifyXML.py b/apprise/plugins/NotifyXML.py index 56ac846e..21ddf0b6 100644 --- a/apprise/plugins/NotifyXML.py +++ b/apprise/plugins/NotifyXML.py @@ -167,7 +167,8 @@ class NotifyXML(NotifyBase): return '{schema}://{auth}{hostname}{port}{fullpath}/?{params}'.format( schema=self.secure_protocol if self.secure else self.protocol, auth=auth, - hostname=NotifyXML.quote(self.host, safe=''), + # never encode hostname since we're expecting it to be a valid one + hostname=self.host, port='' if self.port is None or self.port == default_port else ':{}'.format(self.port), fullpath=NotifyXML.quote(self.fullpath, safe='/'), @@ -267,11 +268,10 @@ class NotifyXML(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyXMPP/__init__.py b/apprise/plugins/NotifyXMPP/__init__.py index a7a73367..48dbc19b 100644 --- a/apprise/plugins/NotifyXMPP/__init__.py +++ b/apprise/plugins/NotifyXMPP/__init__.py @@ -306,7 +306,8 @@ class NotifyXMPP(NotifyBase): return '{schema}://{auth}@{hostname}{port}/{jids}?{params}'.format( auth=auth, schema=default_schema, - hostname=NotifyXMPP.quote(self.host, safe=''), + # never encode hostname since we're expecting it to be a valid one + hostname=self.host, port='' if not self.port or self.port == default_port else ':{}'.format(self.port), jids=jids, @@ -317,11 +318,10 @@ class NotifyXMPP(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ results = NotifyBase.parse_url(url) - if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/plugins/NotifyZulip.py b/apprise/plugins/NotifyZulip.py index 31f949bf..016c44b6 100644 --- a/apprise/plugins/NotifyZulip.py +++ b/apprise/plugins/NotifyZulip.py @@ -352,11 +352,10 @@ class NotifyZulip(NotifyBase): def parse_url(url): """ Parses the URL and returns enough arguments that can allow - us to substantiate this object. + us to re-instantiate this object. """ - results = NotifyBase.parse_url(url) - + results = NotifyBase.parse_url(url, verify_host=False) if not results: # We're done early as we couldn't load the results return results diff --git a/apprise/utils.py b/apprise/utils.py index d1518a0f..1e482747 100644 --- a/apprise/utils.py +++ b/apprise/utils.py @@ -127,52 +127,92 @@ URL_DETECTION_RE = re.compile( REGEX_VALIDATE_LOOKUP = {} -def is_hostname(hostname): - """ - Validate hostname - """ - if len(hostname) > 255 or len(hostname) == 0: - return False - - if hostname[-1] == ".": - hostname = hostname[:-1] - - allowed = re.compile(r'(?!-)[A-Z\d_-]{1,63}(?((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}' + r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$' + ) + match = re_ipv4.match(addr) + if match is not None: + # Return our matched IP + return match.group('ip') + + if ipv6: + # Based on https://stackoverflow.com/questions/53497/\ + # regular-expression-that-matches-valid-ipv6-addresses + # + # IPV6 URLs should be enclosed in square brackets when placed on a URL + # Source: https://tools.ietf.org/html/rfc2732 + # - For this reason, they are additionally checked for existance + re_ipv6 = re.compile( + r'\[?(?P(([0-9a-f]{1,4}:){7,7}[0-9a-f]{1,4}|([0-9a-f]{1,4}:)' + r'{1,7}:|([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}|([0-9a-f]{1,4}:){1,5}' + r'(:[0-9a-f]{1,4}){1,2}|([0-9a-f]{1,4}:){1,4}' + r'(:[0-9a-f]{1,4}){1,3}|([0-9a-f]{1,4}:){1,3}' + r'(:[0-9a-f]{1,4}){1,4}|([0-9a-f]{1,4}:){1,2}' + r'(:[0-9a-f]{1,4}){1,5}|[0-9a-f]{1,4}:' + r'((:[0-9a-f]{1,4}){1,6})|:((:[0-9a-f]{1,4}){1,7}|:)|' + r'fe80:(:[0-9a-f]{0,4}){0,4}%[0-9a-z]{1,}|::' + r'(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]' + r'|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|' + r'1{0,1}[0-9]){0,1}[0-9])|([0-9a-f]{1,4}:){1,4}:((25[0-5]|' + r'(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|' + r'1{0,1}[0-9]){0,1}[0-9])))\]?', re.I, + ) + + match = re_ipv6.match(addr) + if match is not None: + # Return our matched IP between square brackets since that is + # required for URL formatting as per RFC 2732. + return '[{}]'.format(match.group('ip')) + + # There was no match + return False + + +def is_hostname(hostname, ipv4=True, ipv6=True): + """ + Validate hostname + """ + # The entire hostname, including the delimiting dots, has a maximum of 253 + # ASCII characters. + if len(hostname) > 253 or len(hostname) == 0: + return False + + # Strip trailling period on hostname (if one exists) + if hostname[-1] == ".": + hostname = hostname[:-1] + + # Split our hostname up + labels = hostname.split(".") + + # ipv4 check + if len(labels) == 4 and re.match(r'[0-9.]+', hostname): + return is_ipaddr(hostname, ipv4=ipv4, ipv6=False) + + # - RFC 1123 permits hostname labels to start with digits + # - digit must be followed by alpha/numeric so we don't end up + # processing IP addresses here + # - Hostnames can ony be comprised of alpha-numeric characters and the + # hyphen (-) character. + # - Hostnames can not start with the hyphen (-) character. + # - labels can not exceed 63 characters + allowed = re.compile( + r'(?!-)[a-z0-9][a-z0-9-]{1,62}(?.+):(?P[1-9][0-9]{0,4})$', result['host']) + if match: + # Separate our port from our hostname (if port is detected) + result['host'] = match.group('host') + result['port'] = int(match.group('port')) - except ValueError: - # no problem then, user only exists - # and it's already assigned - pass - - if result['port']: - try: - result['port'] = int(result['port']) - - except (ValueError, TypeError): - # Invalid Port Specified + if verify_host: + # Verify and Validate our hostname + result['host'] = is_hostname(result['host']) + if not result['host']: + # Nothing more we can do without a hostname; give the user + # some indication as to what went wrong return None - if result['port'] == 0: - result['port'] = None - - if verify_host and not is_hostname(result['host']): - # Nothing more we can do without a hostname - return None - # Re-assemble cleaned up version of the url result['url'] = '%s://' % result['schema'] if isinstance(result['user'], six.string_types): diff --git a/test/test_glib_plugin.py b/test/test_glib_plugin.py index 07d89e12..30e3d528 100644 --- a/test/test_glib_plugin.py +++ b/test/test_glib_plugin.py @@ -163,9 +163,6 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray, with pytest.raises(TypeError): apprise.plugins.NotifyDBus(**{'schema': 'invalid'}) - # Invalid URLs - assert apprise.plugins.NotifyDBus.parse_url('') is None - # Set our X and Y coordinate and try the notification assert apprise.plugins.NotifyDBus( x_axis=0, y_axis=0, **{'schema': 'dbus'})\ diff --git a/test/test_rest_plugins.py b/test/test_rest_plugins.py index dc7063f4..cdeb9aad 100644 --- a/test/test_rest_plugins.py +++ b/test/test_rest_plugins.py @@ -261,7 +261,11 @@ TEST_URLS = ( # NotifyDiscord ################################## ('discord://', { - 'instance': None, + 'instance': TypeError, + }), + # An invalid url + ('discord://:@/', { + 'instance': TypeError, }), # No webhook_token specified ('discord://%s' % ('i' * 24), { @@ -357,10 +361,6 @@ TEST_URLS = ( # don't include an image by default 'include_image': False, }), - # An invalid url - ('discord://:@/', { - 'instance': None, - }), ('discord://%s/%s/' % ('a' * 24, 'b' * 64), { 'instance': plugins.NotifyDiscord, # force a failure @@ -572,7 +572,10 @@ TEST_URLS = ( # NotifyFaast ################################## ('faast://', { - 'instance': None, + 'instance': TypeError, + }), + ('faast://:@/', { + 'instance': TypeError, }), # Auth Token specified ('faast://%s' % ('a' * 32), { @@ -586,9 +589,6 @@ TEST_URLS = ( # don't include an image by default 'include_image': False, }), - ('faast://:@/', { - 'instance': None, - }), ('faast://%s' % ('a' * 32), { 'instance': plugins.NotifyFaast, # force a failure @@ -613,7 +613,11 @@ TEST_URLS = ( ################################## # No token specified ('flock://', { - 'instance': None, + 'instance': TypeError, + }), + # An invalid url + ('flock://:@/', { + 'instance': TypeError, }), # Provide a token ('flock://%s' % ('t' * 24), { @@ -696,10 +700,6 @@ TEST_URLS = ( # We will still instantiate the object 'instance': plugins.NotifyFlock, }), - # An invalid url - ('flock://:@/', { - 'instance': None, - }), # Error Testing ('flock://%s/g:%s/u:%s?format=text' % ('i' * 24, 'g' * 12, 'u' * 10), { 'instance': plugins.NotifyFlock, @@ -730,10 +730,10 @@ TEST_URLS = ( # NotifyGitter ################################## ('gitter://', { - 'instance': None, + 'instance': TypeError, }), ('gitter://:@/', { - 'instance': None, + 'instance': TypeError, }), # Invalid Token Length ('gitter://%s' % ('a' * 12), { @@ -853,15 +853,15 @@ TEST_URLS = ( # NotifyIFTTT - If This Than That ################################## ('ifttt://', { - 'instance': None, + 'instance': TypeError, + }), + ('ifttt://:@/', { + 'instance': TypeError, }), # No User ('ifttt://EventID/', { 'instance': TypeError, }), - ('ifttt://:@/', { - 'instance': None, - }), # A nicely formed ifttt url with 1 event and a new key/value store ('ifttt://WebHookID@EventID/?+TemplateKey=TemplateVal', { 'instance': plugins.NotifyIFTTT, @@ -917,7 +917,11 @@ TEST_URLS = ( # NotifyJoin ################################## ('join://', { - 'instance': None, + 'instance': TypeError, + }), + # API Key + bad url + ('join://:@/', { + 'instance': TypeError, }), # APIkey; no device ('join://%s' % ('a' * 32), { @@ -970,10 +974,6 @@ TEST_URLS = ( ('join://%s/%s/%s' % ('a' * 32, 'd' * 32, 'group.chrome'), { 'instance': plugins.NotifyJoin, }), - # API Key + bad url - ('join://:@/', { - 'instance': None, - }), ('join://%s' % ('a' * 32), { 'instance': plugins.NotifyJoin, # force a failure @@ -1134,6 +1134,14 @@ TEST_URLS = ( ('kodi://localhost', { 'instance': plugins.NotifyXBMC, }), + ('kodi://192.168.4.1', { + # Support IPv4 Addresses + 'instance': plugins.NotifyXBMC, + }), + ('kodi://[2001:db8:002a:3256:adfe:05c0:0003:0006]', { + # Support IPv6 Addresses + 'instance': plugins.NotifyXBMC, + }), ('kodi://user:pass@localhost', { 'instance': plugins.NotifyXBMC, @@ -1203,14 +1211,14 @@ TEST_URLS = ( ################################## ('kumulos://', { # No API or Server Key specified - 'instance': None, + 'instance': TypeError, }), ('kumulos://:@/', { # No API or Server Key specified # We don't have strict host checking on for kumulos, so this URL # actually becomes parseable and :@ becomes a hostname. # The below errors because a second token wasn't found - 'instance': None, + 'instance': TypeError, }), ('kumulos://{}/'.format(UUID4), { # No server key was specified @@ -1410,10 +1418,10 @@ TEST_URLS = ( # NotifyMailgun ################################## ('mailgun://', { - 'instance': None, + 'instance': TypeError, }), ('mailgun://:@/', { - 'instance': None, + 'instance': TypeError, }), # No Token specified ('mailgun://user@localhost.localdomain', { @@ -1672,7 +1680,7 @@ TEST_URLS = ( # Our expected url(privacy=True) startswith() response: 'privacy_url': 'mmost://localhost:8080/3...4/', }), - ('mmost://localhost:0/3ccdd113474722377935511fc85d3dd4', { + ('mmost://localhost:8080/3ccdd113474722377935511fc85d3dd4', { 'instance': plugins.NotifyMatterMost, }), ('mmost://localhost:invalid-port/3ccdd113474722377935511fc85d3dd4', { @@ -1946,10 +1954,10 @@ TEST_URLS = ( # NotifyNotifico ################################## ('notifico://', { - 'instance': None, + 'instance': TypeError, }), ('notifico://:@/', { - 'instance': None, + 'instance': TypeError, }), ('notifico://1234', { # Just a project id provided (no message token) @@ -2219,7 +2227,11 @@ TEST_URLS = ( # NotifyProwl ################################## ('prowl://', { - 'instance': None, + 'instance': TypeError, + }), + # bad url + ('prowl://:@/', { + 'instance': TypeError, }), # Invalid API Key ('prowl://%s' % ('a' * 20), { @@ -2273,10 +2285,6 @@ TEST_URLS = ( ('prowl://%s' % ('a' * 40), { 'instance': plugins.NotifyProwl, }), - # bad url - ('prowl://:@/', { - 'instance': None, - }), ('prowl://%s' % ('a' * 40), { 'instance': plugins.NotifyProwl, # force a failure @@ -2300,10 +2308,10 @@ TEST_URLS = ( # NotifyPushBullet ################################## ('pbul://', { - 'instance': None, + 'instance': TypeError, }), ('pbul://:@/', { - 'instance': None, + 'instance': TypeError, }), # APIkey ('pbul://%s' % ('a' * 32), { @@ -2439,13 +2447,13 @@ TEST_URLS = ( # NotifyPushSafer ################################## ('psafer://:@/', { - 'instance': None, + 'instance': TypeError, }), ('psafer://', { - 'instance': None, + 'instance': TypeError, }), ('psafers://', { - 'instance': None, + 'instance': TypeError, }), ('psafer://{}'.format('a' * 20), { 'instance': plugins.NotifyPushSafer, @@ -2648,12 +2656,16 @@ TEST_URLS = ( # NotifyPushed ################################## ('pushed://', { - 'instance': None, + 'instance': TypeError, }), # Application Key Only ('pushed://%s' % ('a' * 32), { 'instance': TypeError, }), + # Invalid URL + ('pushed://:@/', { + 'instance': TypeError, + }), # Application Key+Secret ('pushed://%s/%s' % ('a' * 32, 'a' * 64), { 'instance': plugins.NotifyPushed, @@ -2708,9 +2720,6 @@ TEST_URLS = ( # is set and tests that we gracfully handle them 'test_requests_exceptions': True, }), - ('pushed://:@/', { - 'instance': None, - }), ('pushed://%s/%s' % ('a' * 32, 'a' * 64), { 'instance': plugins.NotifyPushed, # force a failure @@ -2810,7 +2819,11 @@ TEST_URLS = ( # NotifyPushover ################################## ('pover://', { - 'instance': None, + 'instance': TypeError, + }), + # bad url + ('pover://:@/', { + 'instance': TypeError, }), # APIkey; no user ('pover://%s' % ('a' * 30), { @@ -2906,10 +2919,6 @@ TEST_URLS = ( ('pover://%s@%s?priority=' % ('u' * 30, 'a' * 30), { 'instance': plugins.NotifyPushover, }), - # bad url - ('pover://:@/', { - 'instance': None, - }), ('pover://%s@%s' % ('u' * 30, 'a' * 30), { 'instance': plugins.NotifyPushover, # force a failure @@ -3115,10 +3124,10 @@ TEST_URLS = ( # NotifyRyver ################################## ('ryver://', { - 'instance': None, + 'instance': TypeError, }), ('ryver://:@/', { - 'instance': None, + 'instance': TypeError, }), ('ryver://apprise', { # Just org provided (no token) @@ -3435,7 +3444,7 @@ TEST_URLS = ( ################################## ('spush://', { # No api key - 'instance': None, + 'instance': TypeError, }), ('spush://{}'.format('A' * 14), { # API Key specified however expected server response @@ -3658,10 +3667,10 @@ TEST_URLS = ( # NotifySNS (AWS) ################################## ('sns://', { - 'instance': None, + 'instance': TypeError, }), ('sns://:@/', { - 'instance': None, + 'instance': TypeError, }), ('sns://T1JJ3T3L2', { # Just Token 1 provided @@ -4132,11 +4141,11 @@ TEST_URLS = ( ################################## ('msg91://', { # No hostname/authkey specified - 'instance': None, + 'instance': TypeError, }), ('msg91://-', { # Invalid AuthKey - 'instance': None, + 'instance': TypeError, }), ('msg91://{}'.format('a' * 23), { # No number specified @@ -4204,7 +4213,7 @@ TEST_URLS = ( ################################## ('msgbird://', { # No hostname/apikey specified - 'instance': None, + 'instance': TypeError, }), ('msgbird://{}/abcd'.format('a' * 25), { # invalid characters in source phone number @@ -4256,7 +4265,7 @@ TEST_URLS = ( ################################## ('popcorn://', { # No hostname/apikey specified - 'instance': None, + 'instance': TypeError, }), ('popcorn://{}/18001231234'.format('_' * 9), { # invalid apikey @@ -4490,10 +4499,10 @@ TEST_URLS = ( # NotifyZulip ################################## ('zulip://', { - 'instance': None, + 'instance': TypeError, }), ('zulip://:@/', { - 'instance': None, + 'instance': TypeError, }), ('zulip://apprise', { # Just org provided (no token or botname) @@ -5010,12 +5019,10 @@ def test_notify_emby_plugin_login(mock_post, mock_get): mock_post.return_value.status_code = requests.codes.ok mock_get.return_value.status_code = requests.codes.ok - obj = Apprise.instantiate('emby://l2g:l2gpass@localhost:%d' % ( - # Increment our port so it will always be something different than - # the default - plugins.NotifyEmby.emby_default_port + 1)) + obj = Apprise.instantiate('emby://l2g:l2gpass@localhost:1234') + # Set a different port (outside of default) assert isinstance(obj, plugins.NotifyEmby) - assert obj.port == (plugins.NotifyEmby.emby_default_port + 1) + assert obj.port == 1234 # The login will fail because '' is not a parseable JSON response assert obj.login() is False @@ -5024,10 +5031,10 @@ def test_notify_emby_plugin_login(mock_post, mock_get): obj.port = None assert obj.login() is False - # Default port assigments + # Default port assignments obj = Apprise.instantiate('emby://l2g:l2gpass@localhost') assert isinstance(obj, plugins.NotifyEmby) - assert obj.port == plugins.NotifyEmby.emby_default_port + assert obj.port == 8096 # The login will (still) fail because '' is not a parseable JSON response assert obj.login() is False diff --git a/test/test_utils.py b/test/test_utils.py index c34f9f97..3a6f9e6d 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -92,16 +92,38 @@ def test_parse_url(): assert result['qsd-'] == {} assert result['qsd+'] == {} - result = utils.parse_url('hostname') + # colon after hostname without port number is no good + assert utils.parse_url('http://hostname:') is None + + # However if we don't verify the host, it is okay + result = utils.parse_url('http://hostname:', verify_host=False) assert result['schema'] == 'http' - assert result['host'] == 'hostname' + assert result['host'] == 'hostname:' assert result['port'] is None assert result['user'] is None assert result['password'] is None assert result['fullpath'] is None assert result['path'] is None assert result['query'] is None - assert result['url'] == 'http://hostname' + assert result['url'] == 'http://hostname:' + assert result['qsd'] == {} + assert result['qsd-'] == {} + assert result['qsd+'] == {} + + # A port of Zero is not valid + assert utils.parse_url('http://hostname:0') is None + + # Port set to zero; port is not stored + result = utils.parse_url('http://hostname:0', verify_host=False) + assert result['schema'] == 'http' + assert result['host'] == 'hostname:0' + assert result['port'] is None + assert result['user'] is None + assert result['password'] is None + assert result['fullpath'] is None + assert result['path'] is None + assert result['query'] is None + assert result['url'] == 'http://hostname:0' assert result['qsd'] == {} assert result['qsd-'] == {} assert result['qsd+'] == {} @@ -314,22 +336,6 @@ def test_parse_url(): assert utils.parse_url('?') is None assert utils.parse_url('/') is None - # A default port of zero is still considered valid, but - # is removed in the response. - result = utils.parse_url('http://nuxref.com:0') - assert result['schema'] == 'http' - assert result['host'] == 'nuxref.com' - assert result['port'] is None - assert result['user'] is None - assert result['password'] is None - assert result['fullpath'] is None - assert result['path'] is None - assert result['query'] is None - assert result['url'] == 'http://nuxref.com' - assert result['qsd'] == {} - assert result['qsd-'] == {} - assert result['qsd+'] == {} - # Test some illegal strings result = utils.parse_url(object, verify_host=False) assert result is None @@ -475,16 +481,55 @@ def test_is_hostname(): """ # Valid Hostnames - assert utils.is_hostname('yahoo.ca') is True - assert utils.is_hostname('yahoo.ca.') is True - assert utils.is_hostname('valid-dashes-in-host.ca') is True - assert utils.is_hostname('valid-underscores_in_host.ca') is True + assert utils.is_hostname('yahoo.ca') == 'yahoo.ca' + assert utils.is_hostname('yahoo.ca.') == 'yahoo.ca' + assert utils.is_hostname('valid-dashes-in-host.ca') == \ + 'valid-dashes-in-host.ca' # Invalid Hostnames + assert utils.is_hostname('-hostname.that.starts.with.a.dash') is False assert utils.is_hostname('invalid-characters_#^.ca') is False assert utils.is_hostname(' spaces ') is False assert utils.is_hostname(' ') is False assert utils.is_hostname('') is False + assert utils.is_hostname('valid-underscores_in_host.ca') is False + + # Valid IPv4 Addresses + assert utils.is_hostname('127.0.0.1') == '127.0.0.1' + assert utils.is_hostname('0.0.0.0') == '0.0.0.0' + assert utils.is_hostname('255.255.255.255') == '255.255.255.255' + + # But not if we're not checking for this: + assert utils.is_hostname('127.0.0.1', ipv4=False) is False + assert utils.is_hostname('0.0.0.0', ipv4=False) is False + assert utils.is_hostname('255.255.255.255', ipv4=False) is False + + # Invalid IPv4 Addresses + assert utils.is_hostname('1.2.3') is False + assert utils.is_hostname('256.256.256.256') is False + assert utils.is_hostname('999.0.0.0') is False + assert utils.is_hostname('1.2.3.4.5') is False + assert utils.is_hostname(' 127.0.0.1 ') is False + assert utils.is_hostname(' ') is False + assert utils.is_hostname('') is False + + # Valid IPv6 Addresses (square brakets supported for URL construction) + assert utils.is_hostname('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]') == \ + '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]' + assert utils.is_hostname('2001:0db8:85a3:0000:0000:8a2e:0370:7334') == \ + '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]' + assert utils.is_hostname('[2001:db8:002a:3256:adfe:05c0:0003:0006]') == \ + '[2001:db8:002a:3256:adfe:05c0:0003:0006]' + + # localhost + assert utils.is_hostname('::1') == '[::1]' + assert utils.is_hostname('0:0:0:0:0:0:0:1') == '[0:0:0:0:0:0:0:1]' + + # But not if we're not checking for this: + assert utils.is_hostname( + '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', ipv6=False) is False + assert utils.is_hostname( + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', ipv6=False) is False def test_is_ipaddr(): @@ -493,9 +538,9 @@ def test_is_ipaddr(): """ # Valid IPv4 Addresses - assert utils.is_ipaddr('127.0.0.1') is True - assert utils.is_ipaddr('0.0.0.0') is True - assert utils.is_ipaddr('255.255.255.255') is True + assert utils.is_ipaddr('127.0.0.1') == '127.0.0.1' + assert utils.is_ipaddr('0.0.0.0') == '0.0.0.0' + assert utils.is_ipaddr('255.255.255.255') == '255.255.255.255' # Invalid IPv4 Addresses assert utils.is_ipaddr('1.2.3') is False @@ -506,8 +551,17 @@ def test_is_ipaddr(): assert utils.is_ipaddr(' ') is False assert utils.is_ipaddr('') is False - # Valid IPv6 Addresses - assert utils.is_ipaddr('2001:0db8:85a3:0000:0000:8a2e:0370:7334') is True + # Valid IPv6 Addresses (square brakets supported for URL construction) + assert utils.is_ipaddr('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]') == \ + '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]' + assert utils.is_ipaddr('2001:0db8:85a3:0000:0000:8a2e:0370:7334') == \ + '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]' + assert utils.is_ipaddr('[2001:db8:002a:3256:adfe:05c0:0003:0006]') == \ + '[2001:db8:002a:3256:adfe:05c0:0003:0006]' + + # localhost + assert utils.is_ipaddr('::1') == '[::1]' + assert utils.is_ipaddr('0:0:0:0:0:0:0:1') == '[0:0:0:0:0:0:0:1]' def test_is_email():