Emails - parse host from user login if not specified (#1095)

This commit is contained in:
Chris Caron 2024-04-07 14:09:32 -04:00 committed by GitHub
parent 6cd528cd5c
commit 1cef49e198
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 225 additions and 12 deletions

View File

@ -45,7 +45,7 @@ from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..common import NotifyFormat, NotifyType
from ..conversion import convert_between
from ..utils import is_email, parse_emails
from ..utils import is_email, parse_emails, is_hostname
from ..AppriseLocale import gettext_lazy as _
from ..logger import logger
@ -566,12 +566,20 @@ class NotifyEmail(NotifyBase):
# Apply any defaults based on certain known configurations
self.NotifyEmailDefaults(secure_mode=secure_mode, **kwargs)
if self.user and self.host:
# Prepare the bases of our email
self.from_addr = [self.app_id, '{}@{}'.format(
re.split(r'[\s@]+', self.user)[0],
self.host,
)]
if self.user:
if self.host:
# Prepare the bases of our email
self.from_addr = [self.app_id, '{}@{}'.format(
re.split(r'[\s@]+', self.user)[0],
self.host,
)]
else:
result = is_email(self.user)
if result:
# Prepare the bases of our email and include domain
self.host = result['domain']
self.from_addr = [self.app_id, self.user]
if from_addr:
result = is_email(from_addr)
@ -1037,11 +1045,25 @@ class NotifyEmail(NotifyBase):
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
# Prepare our target lists
results['targets'] = []
if not is_hostname(results['host'], ipv4=False, ipv6=False,
underscore=False):
if is_email(NotifyEmail.unquote(results['host'])):
# Don't lose defined email addresses
results['targets'].append(NotifyEmail.unquote(results['host']))
# Detect if we have a valid hostname or not; be sure to reset it's
# value if invalid; we'll attempt to figure this out later on
results['host'] = ''
# The From address is a must; either through the use of templates
# from= entry and/or merging the user and hostname together, this
# must be calculated or parse_url will fail.
@ -1052,7 +1074,7 @@ class NotifyEmail(NotifyBase):
# Get our potential email targets; if none our found we'll just
# add one to ourselves
results['targets'] = NotifyEmail.split_path(results['fullpath'])
results['targets'] += NotifyEmail.split_path(results['fullpath'])
# Attempt to detect 'to' email address
if 'to' in results['qsd'] and len(results['qsd']['to']):

View File

@ -27,6 +27,7 @@
# POSSIBILITY OF SUCH DAMAGE.
import logging
import pytest
import os
import re
from unittest import mock
@ -56,13 +57,13 @@ TEST_URLS = (
# NotifyEmail
##################################
('mailto://', {
'instance': None,
'instance': TypeError,
}),
('mailtos://', {
'instance': None,
'instance': TypeError,
}),
('mailto://:@/', {
'instance': None
'instance': TypeError,
}),
# No Username
('mailtos://:pass@nuxref.com:567', {
@ -441,9 +442,12 @@ def test_plugin_email(mock_smtp, mock_smtpssl):
except Exception as e:
# Handle our exception
if instance is None:
print('%s generated %s' % (url, str(e)))
raise
if not isinstance(e, instance):
print('%s Exception (expected %s); got %s' % (
url, str(instance), str(e)))
raise
@ -1815,3 +1819,190 @@ def test_plugin_email_variables_1087():
assert email.smtp_host == 'smtp.alt.lan'
assert email.targets == [(False, 'alteriks@alt.lan')]
assert email.password == 'abcd'
@mock.patch('smtplib.SMTP_SSL')
@mock.patch('smtplib.SMTP')
def test_plugin_host_detection_from_source_email(mock_smtp, mock_smtp_ssl):
"""
NotifyEmail() Discord Issue reporting that the following did not work:
mailtos://?smtp=mobile.charter.net&pass=password&user=name@spectrum.net
"""
response = mock.Mock()
mock_smtp_ssl.return_value = response
mock_smtp.return_value = response
results = NotifyEmail.parse_url(
'mailtos://spectrum.net?smtp=mobile.charter.net'
'&pass=password&user=name@spectrum.net')
assert isinstance(results, dict)
assert 'name@spectrum.net' == results['user']
assert 'spectrum.net' == results['host']
assert 'mobile.charter.net' == results['smtp_host']
assert 'password' == results['password']
obj = Apprise.instantiate(results, suppress_exceptions=False)
assert isinstance(obj, NotifyEmail) is True
assert len(obj.targets) == 1
assert (False, 'name@spectrum.net') in obj.targets
assert obj.from_addr[0] == obj.app_id
assert obj.from_addr[1] == 'name@spectrum.net'
assert obj.password == 'password'
assert obj.user == 'name@spectrum.net'
assert obj.secure is True
assert obj.port == 587
assert obj.smtp_host == 'mobile.charter.net'
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert obj.notify('body', 'title') is True
assert mock_smtp.call_count == 1
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 1
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
# Store our Sent Arguments
# Syntax is:
# sendmail(from_addr, to_addrs, msg, mail_options=(), rcpt_options=())
# [0] [1] [2]
_from = response.sendmail.call_args[0][0]
_to = response.sendmail.call_args[0][1]
_msg = response.sendmail.call_args[0][2]
assert _from == 'name@spectrum.net'
assert isinstance(_to, list)
assert len(_to) == 1
assert _to[0] == 'name@spectrum.net'
assert _msg.split('\n')[-3] == 'body'
#
# Now let's do a shortened version of the same URL where the host isn't
# specified but is parseable from he user login
#
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
results = NotifyEmail.parse_url(
'mailtos://?smtp=mobile.charter.net'
'&pass=password&user=name@spectrum.net')
assert isinstance(results, dict)
assert 'name@spectrum.net' == results['user']
assert '' == results['host'] # No hostname defined; it's detected later
assert 'mobile.charter.net' == results['smtp_host']
assert 'password' == results['password']
obj = Apprise.instantiate(results, suppress_exceptions=False)
assert isinstance(obj, NotifyEmail) is True
assert len(obj.targets) == 1
assert (False, 'name@spectrum.net') in obj.targets
assert obj.from_addr[0] == obj.app_id
assert obj.from_addr[1] == 'name@spectrum.net'
assert obj.password == 'password'
assert obj.user == 'name@spectrum.net'
assert obj.secure is True
assert obj.port == 587
assert obj.smtp_host == 'mobile.charter.net'
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert obj.notify('body', 'title') is True
assert mock_smtp.call_count == 1
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 1
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
# Store our Sent Arguments
# Syntax is:
# sendmail(from_addr, to_addrs, msg, mail_options=(), rcpt_options=())
# [0] [1] [2]
_from = response.sendmail.call_args[0][0]
_to = response.sendmail.call_args[0][1]
_msg = response.sendmail.call_args[0][2]
assert _from == 'name@spectrum.net'
assert isinstance(_to, list)
assert len(_to) == 1
assert _to[0] == 'name@spectrum.net'
assert _msg.split('\n')[-3] == 'body'
#
# Now let's do a shortened version of the same URL where the host isn't
# specified but is parseable from he user login
#
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
results = NotifyEmail.parse_url(
'mailtos://?smtp=mobile.charter.net'
'&pass=password&user=userid-without-domain')
assert isinstance(results, dict)
assert 'userid-without-domain' == results['user']
assert '' == results['host'] # No hostname defined
assert 'mobile.charter.net' == results['smtp_host']
assert 'password' == results['password']
with pytest.raises(TypeError):
# We will fail
Apprise.instantiate(results, suppress_exceptions=False)
#
# Now support target emails in place of the hostname
#
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
results = NotifyEmail.parse_url(
'mailtos://John Doe<john%40yahoo.ca>?smtp=mobile.charter.net'
'&pass=password&user=name@spectrum.net')
assert isinstance(results, dict)
assert 'name@spectrum.net' == results['user']
assert '' == results['host'] # No hostname defined; it's detected later
assert 'mobile.charter.net' == results['smtp_host']
assert 'password' == results['password']
obj = Apprise.instantiate(results, suppress_exceptions=False)
assert isinstance(obj, NotifyEmail) is True
assert len(obj.targets) == 1
assert ('John Doe', 'john@yahoo.ca') in obj.targets
assert obj.from_addr[0] == obj.app_id
assert obj.from_addr[1] == 'name@spectrum.net'
assert obj.password == 'password'
assert obj.user == 'name@spectrum.net'
assert obj.secure is True
assert obj.port == 587
assert obj.smtp_host == 'mobile.charter.net'
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert obj.notify('body', 'title') is True
assert mock_smtp.call_count == 1
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 1
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
# Store our Sent Arguments
# Syntax is:
# sendmail(from_addr, to_addrs, msg, mail_options=(), rcpt_options=())
# [0] [1] [2]
_from = response.sendmail.call_args[0][0]
_to = response.sendmail.call_args[0][1]
_msg = response.sendmail.call_args[0][2]
assert _from == 'name@spectrum.net'
assert isinstance(_to, list)
assert len(_to) == 1
assert _to[0] == 'john@yahoo.ca'
assert _msg.split('\n')[-3] == 'body'