Skip to content

Commit dabbdad

Browse files
committed
Properly encode path components used to construct API URLs
Resolves #144 and similar potential issues
1 parent 578bad9 commit dabbdad

File tree

4 files changed

+18
-8
lines changed

4 files changed

+18
-8
lines changed

anymail/backends/mailgun.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from datetime import datetime
22
from email.utils import encode_rfc2231
3-
from six.moves.urllib.parse import quote_plus
3+
from six.moves.urllib.parse import quote
44

55
from requests import Request
66

@@ -82,12 +82,12 @@ def get_api_endpoint(self):
8282
"Either provide valid `from_email`, "
8383
"or set `message.esp_extra={'sender_domain': 'example.com'}`",
8484
backend=self.backend, email_message=self.message, payload=self)
85-
if '/' in self.sender_domain or '%' in self.sender_domain:
85+
if '/' in self.sender_domain or '%2f' in self.sender_domain.lower():
8686
# Mailgun returns a cryptic 200-OK "Mailgun Magnificent API" response
8787
# if '/' (or even %-encoded '/') confuses it about the API endpoint.
88-
raise AnymailError("Invalid sender domain '%s'" % self.sender_domain,
88+
raise AnymailError("Invalid '/' in sender domain '%s'" % self.sender_domain,
8989
backend=self.backend, email_message=self.message, payload=self)
90-
return "%s/messages" % quote_plus(self.sender_domain)
90+
return "%s/messages" % quote(self.sender_domain, safe='')
9191

9292
def get_request_params(self, api_url):
9393
params = super(MailgunPayload, self).get_request_params(api_url)

anymail/backends/mailjet.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from six.moves.urllib.parse import quote
2+
13
from ..exceptions import AnymailRequestsAPIError
24
from ..message import AnymailRecipientStatus, ANYMAIL_STATUSES
35
from ..utils import get_anymail_setting, EmailAddress, parse_address_list
@@ -131,7 +133,7 @@ def _populate_sender_from_template(self):
131133
template_id = self.data.get("Mj-TemplateID")
132134
if template_id and not self.data.get("FromEmail"):
133135
response = self.backend.session.get(
134-
"%sREST/template/%s/detailcontent" % (self.backend.api_url, template_id),
136+
"%sREST/template/%s/detailcontent" % (self.backend.api_url, quote(str(template_id), safe='')),
135137
auth=self.auth, timeout=self.backend.timeout
136138
)
137139
self.backend.raise_for_status(response, None, self.message)

anymail/backends/sendinblue.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from requests.structures import CaseInsensitiveDict
2+
from six.moves.urllib.parse import quote
23

34
from .base_requests import AnymailRequestsBackend, RequestsPayload
45
from ..exceptions import AnymailRequestsAPIError
@@ -76,7 +77,7 @@ def __init__(self, message, defaults, backend, *args, **kwargs):
7677

7778
def get_api_endpoint(self):
7879
if self.template_id:
79-
return "smtp/templates/%s/send" % self.template_id
80+
return "smtp/templates/%s/send" % quote(str(self.template_id), safe='')
8081
else:
8182
return "smtp/email"
8283

tests/test_mailgun_backend.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -565,17 +565,24 @@ def test_invalid_sender_domain(self):
565565
# (which returns a cryptic 200-OK "Mailgun Magnificent API" response).
566566
self.message.from_email = "<[email protected]/invalid>"
567567
with self.assertRaisesMessage(AnymailError,
568-
"Invalid sender domain 'example.com/invalid'"):
568+
"Invalid '/' in sender domain 'example.com/invalid'"):
569569
self.message.send()
570570

571571
@override_settings(ANYMAIL_MAILGUN_SENDER_DOMAIN='example.com%2Finvalid')
572572
def test_invalid_sender_domain_setting(self):
573573
# See previous test. Also, note that Mailgun unquotes % encoding *before*
574574
# extracting the sender domain (so %2f is just as bad as '/')
575575
with self.assertRaisesMessage(AnymailError,
576-
"Invalid sender domain 'example.com%2Finvalid'"):
576+
"Invalid '/' in sender domain 'example.com%2Finvalid'"):
577577
self.message.send()
578578

579+
@override_settings(ANYMAIL_MAILGUN_SENDER_DOMAIN='example.com # oops')
580+
def test_encode_sender_domain(self):
581+
# See previous tests. For anything other than slashes, we let Mailgun detect
582+
# the problem (but must properly encode the domain in the API URL)
583+
self.message.send()
584+
self.assert_esp_called('/example.com%20%23%20oops/messages')
585+
579586
def test_default_omits_options(self):
580587
"""Make sure by default we don't send any ESP-specific options.
581588

0 commit comments

Comments
 (0)