anymail / django-anymail

Django email backends and webhooks for Amazon SES, Brevo (Sendinblue), MailerSend, Mailgun, Mailjet, Postmark, Postal, Resend, SendGrid, SparkPost, Unisender Go and more
https://anymail.dev
BSD 3-Clause "New" or "Revised" License
1.67k stars 129 forks source link

Postmark Cc email address can not be looped over if empty #307

Closed Ecno92 closed 1 year ago

Ecno92 commented 1 year ago

When a postmark Cc email address is empty we can not loop over the result without getting an exception.

Below you can find an minimal example using parts of the codebase:

# This is how an empty cc looks in the esp_event from postmark
In [12]: esp_event = {"ccFull": []}

# This is how it is processed in anymail
# https://github.com/anymail/django-anymail/blob/main/anymail/webhooks/postmark.py#L183
In [13]: cc=", ".join([self._address(cc) for cc in esp_event.get("CcFull", [])])

# Now we are left with an empty string
In [14]: cc
Out[14]: ''

# After constructing the message it remains an empty string
In [15]: message = AnymailInboundMessage.construct(cc=cc)

# If you then try to loop over the cc email addresses in the message you get an exception
In [16]: [print(x) for x in message.cc]
---------------------------------------------------------------------------
AnymailInvalidAddress                     Traceback (most recent call last)
Cell In[16], line 1
----> 1 [print(x) for x in message.cc]

File /opt/venv/lib/python3.10/site-packages/anymail/inbound.py:72, in AnymailInboundMessage.cc(self)
     70 """list of EmailAddress objects from Cc header"""
     71 # equivalent to Python 3.2+ message['Cc'].addresses
---> 72 return self.get_address_header("Cc")

File /opt/venv/lib/python3.10/site-packages/anymail/inbound.py:116, in AnymailInboundMessage.get_address_header(self, header)
    114 values = self.get_all(header)
    115 if values is not None:
--> 116     values = parse_address_list(values)
    117 return values or []

File /opt/venv/lib/python3.10/site-packages/anymail/utils.py:170, in parse_address_list(address_list, field)
    168         if len(parsed) > len(address_list):
    169             errmsg += " (Maybe missing quotes around a display-name?)"
--> 170         raise AnymailInvalidAddress(errmsg)
    172 return parsed

AnymailInvalidAddress: Invalid email address '' parsed from ''.

I would expect that .cc is set to [] in case no cc email address is set on the incoming message. Is this indeed a bug or are my expectations not correct? Also I would not expect that parsing errors occur after the parsing procedure.

Let me know what you think.

medmunds commented 1 year ago

Thanks for the report. Yes, this seems to be a bug in AnymailInboundMessage.construct(): when called with an empty string to or cc param, it results in a message with an empty-string To or Cc header. The correct behavior would be to have no To or Cc header in those cases. The problem doesn't become apparent until you try to access those values using AnymailInboundMessage.to or .cc, at which point the invalid empty header is detected.

Postmark seems to be the only ESP using the to and cc params to AnymailInboundMessage.construct().