Repository: pypi/warehouse

Issue:
Author from pyproject.toml is displayed incorect
<!--
    NOTE: This issue should be for problems with PyPI itself, including:
    * pypi.org
    * test.pypi.org
    * files.pythonhosted.org

    This issue should NOT be for a project installed from PyPI. If you are
    having an issue with a specific package, you should reach out to the
    maintainers of that project directly instead.

    Furthermore, this issue should NOT be for any non-PyPI properties (like
    python.org, docs.python.org, etc.)

    If your problem is related to search (a new or updated project doesn't
    appear in the PyPI search results), please wait for a couple of hours
    and check again before reporting it. The search index may take some
    time to be updated.
-->

**Describe the bug**
I had made a new project with a pyprorejt.toml. MY pyproject.toml contains author information this form:
```toml
authors = [
  { name="Example Author", email="author@example.com" },
]
```
This is the right form [according to the official packaging guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/#configuring-metadata). But the author is  displayed wrong on PyPI.

**Expected behavior**
![grafik](https://user-images.githubusercontent.com/15185051/181994557-48b349bd-38f8-4384-9eef-64b0db51a7e4.png)

**To Reproduce**
Visit my [new project](https://pypi.org/project/desktop-entry-lib/). You will see this:
![grafik](https://user-images.githubusercontent.com/15185051/181994587-eb2f92ea-d6de-44c8-9e9d-11808546243e.png)
It is also in the mailto link, which leads to a invalid mailto.

**My Platform**
Any platform

**Additional context**
I first thought that was a bug in the build module, so I [reported it there](https://github.com/pypa/build/issues/501). But according to the PEPs the behaviour of the build module is right and the Bug is on the side of PyPI.



Discussion:
This is a funny case: [RFC 2328](https://www.rfc-editor.org/rfc/rfc2368) allows `mailto:` to contain "mailbox"-style addesses (like `Foo Bar <foo.bar@example.com>`), while [RFC 6068](https://datatracker.ietf.org/doc/html/rfc6068) (which supersedes 2328) only allows the address-specification form. 

PyPI should arguably follow the latest RFC, but it seems like support for the older one is still common (for example, macOS's `Mail.app` has no problems with the `mailto:Foo Bar <foo.bar@example.com>` format.

So, arguably, the `mailto:` URL itself is not invalid. We _could_ probably render the author without their email in angle brackets, but there might be additional data quality issues (e.g. authors with emails only, no names) that make that annoying.
I think today warehouse is echoing the input from the metadata only, nothing more. It's stored as an unstructured text column.

I'm exploring a custom jinja filter that would format it correctly on display. 

Resolving patch:
diff --git a/warehouse/config.py b/warehouse/config.py
--- a/warehouse/config.py
+++ b/warehouse/config.py
@@ -421,6 +421,7 @@ def configure(settings=None):
     filters.setdefault("localize_datetime", "warehouse.filters:localize_datetime")
     filters.setdefault("is_recent", "warehouse.filters:is_recent")
     filters.setdefault("canonicalize_name", "packaging.utils:canonicalize_name")
+    filters.setdefault("format_author_email", "warehouse.filters:format_author_email")
 
     # We also want to register some global functions for Jinja
     jglobals = config.get_settings().setdefault("jinja2.globals", {})
diff --git a/warehouse/filters.py b/warehouse/filters.py
--- a/warehouse/filters.py
+++ b/warehouse/filters.py
@@ -19,6 +19,8 @@
 import re
 import urllib.parse
 
+from email.utils import getaddresses
+
 import html5lib
 import html5lib.serializer
 import html5lib.treewalkers
@@ -163,5 +165,19 @@ def is_recent(timestamp):
     return False
 
 
+def format_author_email(metadata_email: str) -> tuple[str, str]:
+    """
+    Return the name and email address from a metadata RFC-822 string.
+    Use Jinja's `first` and `last` to access each part in a template.
+    TODO: Support more than one email address, per RFC-822.
+    """
+    author_emails = []
+    for author_name, author_email in getaddresses([metadata_email]):
+        if "@" not in author_email:
+            return author_name, ""
+        author_emails.append((author_name, author_email))
+    return author_emails[0][0], author_emails[0][1]
+
+
 def includeme(config):
     config.add_request_method(_camo_url, name="camo_url")


Test patch:
diff --git a/tests/unit/test_filters.py b/tests/unit/test_filters.py
--- a/tests/unit/test_filters.py
+++ b/tests/unit/test_filters.py
@@ -227,3 +227,17 @@ def test_is_recent(delta, expected):
 
 def test_is_recent_none():
     assert filters.is_recent(None) is False
+
+
+@pytest.mark.parametrize(
+    ("meta_email", "expected_name", "expected_email"),
+    [
+        ("not-an-email-address", "", ""),
+        ("foo@bar.com", "", "foo@bar.com"),
+        ('"Foo Bar" <foo@bar.com>', "Foo Bar", "foo@bar.com"),
+    ],
+)
+def test_format_author_email(meta_email, expected_name, expected_email):
+    author_name, author_email = filters.format_author_email(meta_email)
+    assert author_name == expected_name
+    assert author_email == expected_email