Repository: freedomofpress/securedrop

Issue:
invalid `datetime` attributes
## Description

In `securedrop/source_templates/lookup.html`, `time` elements have invalid `datetime` attributes giving too many digits for fractional seconds.

## Steps to Reproduce

Validate the HTML of the Source Interface's `/lookup` view.

## Expected Behavior

The page validates.

## Actual Behavior

The page fails validation with errors like:

> Bad value `2021-07-13 22:47:04.564322` for attribute `datetime` on element `time`: A fraction of a second must be one, two, or three digits

## Comments

Should be simple to fix with a truncating Jinja filter.
invalid `datetime` attributes
## Description

In `securedrop/source_templates/lookup.html`, `time` elements have invalid `datetime` attributes giving too many digits for fractional seconds.

## Steps to Reproduce

Validate the HTML of the Source Interface's `/lookup` view.

## Expected Behavior

The page validates.

## Actual Behavior

The page fails validation with errors like:

> Bad value `2021-07-13 22:47:04.564322` for attribute `datetime` on element `time`: A fraction of a second must be one, two, or three digits

## Comments

Should be simple to fix with a truncating Jinja filter.


Discussion:
I have fixed this in #6058, however I used strftime() instead of the Jinja truncate feature, as you cannot truncate the datetime instance without first converting it to a string.
Thanks to @cfm for finding this and @anorthall for picking it up! Will add a comment on the PR.
I have fixed this in #6058, however I used strftime() instead of the Jinja truncate feature, as you cannot truncate the datetime instance without first converting it to a string.
Thanks to @cfm for finding this and @anorthall for picking it up! Will add a comment on the PR.

Resolving patch:
diff --git a/securedrop/journalist_app/__init__.py b/securedrop/journalist_app/__init__.py
--- a/securedrop/journalist_app/__init__.py
+++ b/securedrop/journalist_app/__init__.py
@@ -115,6 +115,8 @@ def _handle_http_exception(
     app.jinja_env.filters['rel_datetime_format'] = \
         template_filters.rel_datetime_format
     app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat
+    app.jinja_env.filters['html_datetime_format'] = \
+        template_filters.html_datetime_format
 
     @app.before_first_request
     def expire_blacklisted_tokens() -> None:
diff --git a/securedrop/source_app/__init__.py b/securedrop/source_app/__init__.py
--- a/securedrop/source_app/__init__.py
+++ b/securedrop/source_app/__init__.py
@@ -98,6 +98,8 @@ def handle_csrf_error(e: CSRFError) -> werkzeug.Response:
         template_filters.rel_datetime_format
     app.jinja_env.filters['nl2br'] = evalcontextfilter(template_filters.nl2br)
     app.jinja_env.filters['filesizeformat'] = template_filters.filesizeformat
+    app.jinja_env.filters['html_datetime_format'] = \
+        template_filters.html_datetime_format
 
     for module in [main, info, api]:
         app.register_blueprint(module.make_blueprint(config))  # type: ignore
diff --git a/securedrop/template_filters.py b/securedrop/template_filters.py
--- a/securedrop/template_filters.py
+++ b/securedrop/template_filters.py
@@ -49,3 +49,8 @@ def filesizeformat(value: int) -> str:
         prefix = prefixes[i]
         bytes = float(value) / base ** (i + 1)
         return units.format_unit(bytes, prefix, locale=locale, length="short")
+
+
+def html_datetime_format(dt: datetime) -> str:
+    """Return a datetime string that will pass HTML validation"""
+    return dates.format_datetime(dt, 'yyyy-MM-dd HH:mm:ss.SSS')


Test patch: