attachment code hardening + 100% test coverage

pull/173/head
Chris Caron 2019-11-11 11:34:25 -05:00
parent 717910e969
commit 002cebfaa3
9 changed files with 143 additions and 13 deletions

View File

@ -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):

View File

@ -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

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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():
"""