diff --git a/apprise/Apprise.py b/apprise/Apprise.py index b0445e82..31bd2888 100644 --- a/apprise/Apprise.py +++ b/apprise/Apprise.py @@ -314,9 +314,14 @@ class Apprise(object): # Tracks conversions conversion_map = dict() - # Prepare attachments + # Prepare attachments if required if attach is not None and not isinstance(attach, AppriseAttachment): - attach = AppriseAttachment(attach, asset=self.asset) + try: + attach = AppriseAttachment(attach, asset=self.asset) + + except TypeError: + # bad attachments + return False # Iterate over our loaded plugins for server in self.find(tag): diff --git a/apprise/AppriseAttachment.py b/apprise/AppriseAttachment.py index 68319365..ee046582 100644 --- a/apprise/AppriseAttachment.py +++ b/apprise/AppriseAttachment.py @@ -26,7 +26,6 @@ import six from . import attachment -from . import AttachBase from . import URLBase from .AppriseAsset import AppriseAsset from .logger import logger @@ -58,7 +57,9 @@ class AppriseAttachment(object): # Now parse any paths specified if paths is not None: # Store our path(s) - self.add(paths) + if not self.add(paths): + # Parse Source domain based on from_addr + raise TypeError("One or more attachments could not be added.") def add(self, attachments, asset=None, db=None): """ @@ -72,7 +73,7 @@ class AppriseAttachment(object): # prepare default asset asset = self.asset - if isinstance(attachments, AttachBase): + if isinstance(attachments, attachment.AttachBase): # Go ahead and just add our attachments into our list self.attachments.append(attachments) return True @@ -90,7 +91,7 @@ class AppriseAttachment(object): # Iterate over our attachments for _attachment in attachments: - if isinstance(_attachment, AttachBase): + if isinstance(_attachment, attachment.AttachBase): # Go ahead and just add our attachment into our list self.attachments.append(_attachment) continue @@ -107,7 +108,7 @@ class AppriseAttachment(object): # Instantiate ourselves an object, this function throws or # returns None if it fails instance = AppriseAttachment.instantiate(_attachment, asset=asset) - if not isinstance(instance, AttachBase): + if not isinstance(instance, attachment.AttachBase): return_status = False continue diff --git a/apprise/attachment/AttachHTTP.py b/apprise/attachment/AttachHTTP.py index 7148e340..cc45a427 100644 --- a/apprise/attachment/AttachHTTP.py +++ b/apprise/attachment/AttachHTTP.py @@ -131,10 +131,15 @@ class AttachHTTP(AttachBase): # Handle Errors r.raise_for_status() - # Store our response if within reason - if self.max_file_size > 0 \ - and r.headers.get( - 'Content-Length', 0) > self.max_file_size: + # Get our file-size (if known) + try: + file_size = int(r.headers.get('Content-Length', '0')) + except (TypeError, ValueError): + # Handle edge case where Content-Length is a bad value + file_size = 0 + + # Perform a little Q/A on file limitations and restrictions + if self.max_file_size > 0 and file_size > self.max_file_size: # The content retrieved is to large self.logger.error( diff --git a/apprise/plugins/NotifyBase.py b/apprise/plugins/NotifyBase.py index 2b665baa..7e963b0c 100644 --- a/apprise/plugins/NotifyBase.py +++ b/apprise/plugins/NotifyBase.py @@ -33,6 +33,7 @@ from ..common import NOTIFY_FORMATS from ..common import OverflowMode from ..common import OVERFLOW_MODES from ..AppriseLocale import gettext_lazy as _ +from ..AppriseAttachment import AppriseAttachment class NotifyBase(URLBase): @@ -247,6 +248,15 @@ class NotifyBase(URLBase): """ + # Prepare attachments if required + if attach is not None and not isinstance(attach, AppriseAttachment): + try: + attach = AppriseAttachment(attach, asset=self.asset) + + except TypeError: + # bad attachments + return False + # Handle situations where the title is None title = '' if not title else title diff --git a/apprise/plugins/NotifyEmail.py b/apprise/plugins/NotifyEmail.py index 130436ed..d903ca55 100644 --- a/apprise/plugins/NotifyEmail.py +++ b/apprise/plugins/NotifyEmail.py @@ -27,8 +27,9 @@ import re import six import smtplib from email.mime.text import MIMEText -from email.MIMEMultipart import MIMEMultipart from email.mime.application import MIMEApplication +from email.mime.multipart import MIMEMultipart + from socket import error as SocketError from datetime import datetime diff --git a/test/test_api.py b/test/test_api.py index 5426b73a..2e30db9d 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -33,9 +33,11 @@ import mock from os import chmod from os import getuid from os.path import dirname +from os.path import join from apprise import Apprise from apprise import AppriseAsset +from apprise import AppriseAttachment from apprise import NotifyBase from apprise import NotifyType from apprise import NotifyFormat @@ -54,6 +56,9 @@ import inspect import logging logging.disable(logging.CRITICAL) +# Attachment Directory +TEST_VAR_DIR = join(dirname(__file__), 'var') + def test_apprise(): """ @@ -166,7 +171,7 @@ def test_apprise(): # Support URL return '' - def notify(self, **kwargs): + def send(self, **kwargs): # Pretend everything is okay return True @@ -200,6 +205,39 @@ def test_apprise(): assert a.notify(title='present', body=None) is True assert a.notify(title="present", body="present") is True + # Send Attachment with success + attach = join(TEST_VAR_DIR, 'apprise-test.gif') + assert a.notify( + body='body', title='test', notify_type=NotifyType.INFO, + attach=attach) is True + + # Send the attachment as an AppriseAttachment object + assert a.notify( + body='body', title='test', notify_type=NotifyType.INFO, + attach=AppriseAttachment(attach)) is True + + # test a invalid attachment + assert a.notify( + body='body', title='test', notify_type=NotifyType.INFO, + attach='invalid://') is False + + # Repeat the same tests above... + # however do it by directly accessing the object; this grants the similar + # results: + assert a[0].notify( + body='body', title='test', notify_type=NotifyType.INFO, + attach=attach) is True + + # Send the attachment as an AppriseAttachment object + assert a[0].notify( + body='body', title='test', notify_type=NotifyType.INFO, + attach=AppriseAttachment(attach)) is True + + # test a invalid attachment + assert a[0].notify( + body='body', title='test', notify_type=NotifyType.INFO, + attach='invalid://') is False + # Clear our server listings again a.clear() diff --git a/test/test_apprise_attachments.py b/test/test_apprise_attachments.py index ae3e4173..7ac07826 100644 --- a/test/test_apprise_attachments.py +++ b/test/test_apprise_attachments.py @@ -140,6 +140,11 @@ def test_apprise_attachment(): assert len(aa) == 0 assert not aa + # if instantiating attachments from the class, it will throw a TypeError + # if attachments couldn't be loaded + with pytest.raises(TypeError): + AppriseAttachment('garbage://') + # Garbage in produces garbage out assert aa.add(None) is False assert aa.add(object()) is False diff --git a/test/test_attach_http.py b/test/test_attach_http.py index 674659e0..277d60ed 100644 --- a/test/test_attach_http.py +++ b/test/test_attach_http.py @@ -238,6 +238,7 @@ def test_attach_http(mock_get): # Set our limit to be the length of our image; everything should work # without a problem + max_file_size = AttachHTTP.max_file_size AttachHTTP.max_file_size = getsize(path) # Set ourselves a Content-Disposition (providing a filename) dummy_response.headers['Content-Disposition'] = \ @@ -298,6 +299,25 @@ def test_attach_http(mock_get): assert attachment assert len(attachment) == getsize(path) + # Set our header up with an invalid Content-Length; we can still process + # this data. It just means we track it lower when reading back content + dummy_response.headers = { + 'Content-Length': 'invalid' + } + results = AttachHTTP.parse_url('http://localhost/invalid-length.gif') + assert isinstance(results, dict) + attachment = AttachHTTP(**results) + assert isinstance(attachment.url(), six.string_types) is True + # No mime-type and/or filename over-ride was specified, so therefore it + # won't show up in the generated URL + assert re.search(r'[?&]mime=', attachment.url()) is None + assert re.search(r'[?&]name=', attachment.url()) is None + assert attachment.mimetype == 'image/gif' + # Because we could determine our mime type, we could build an extension + # for our unknown filename + assert attachment.name == 'invalid-length.gif' + assert attachment + # Give ourselves nothing to work with dummy_response.headers = {} results = AttachHTTP.parse_url('http://user@localhost') @@ -328,3 +348,6 @@ def test_attach_http(mock_get): mock_get.side_effect = _exception assert not aa + + # Restore value + AttachHTTP.max_file_size = max_file_size diff --git a/test/test_email_plugin.py b/test/test_email_plugin.py index 76abbc89..c7ceb20e 100644 --- a/test/test_email_plugin.py +++ b/test/test_email_plugin.py @@ -23,6 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +import os import re import six import mock @@ -31,12 +32,17 @@ import smtplib from apprise import plugins from apprise import NotifyType from apprise import Apprise +from apprise import AttachBase +from apprise import AppriseAttachment from apprise.plugins import NotifyEmailBase # Disable logging for a cleaner testing output import logging logging.disable(logging.CRITICAL) +# Attachment Directory +TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var') + TEST_URLS = ( ################################## @@ -462,6 +468,42 @@ def test_smtplib_send_okay(mock_smtplib): assert obj.notify( body='body', title='test', notify_type=NotifyType.INFO) is True + # Create an apprise object to work with as well + a = Apprise() + assert a.add('mailto://user:pass@gmail.com?format=text') + + # Send Attachment with success + attach = os.path.join(TEST_VAR_DIR, 'apprise-test.gif') + assert obj.notify( + body='body', title='test', notify_type=NotifyType.INFO, + attach=attach) is True + + # same results happen from our Apprise object + assert a.notify(body='body', title='test', attach=attach) is True + + # test using an Apprise Attachment object + assert obj.notify( + body='body', title='test', notify_type=NotifyType.INFO, + attach=AppriseAttachment(attach)) is True + + # same results happen from our Apprise object + assert a.notify( + body='body', title='test', attach=AppriseAttachment(attach)) is True + + max_file_size = AttachBase.max_file_size + # Now do a case where the file can't be sent + + AttachBase.max_file_size = 1 + assert obj.notify( + body='body', title='test', notify_type=NotifyType.INFO, + attach=attach) is False + + # same results happen from our Apprise object + assert a.notify(body='body', title='test', attach=attach) is False + + # Restore value + AttachBase.max_file_size = max_file_size + def test_email_url_escaping(): """