From 816c34aec09ece3188b0412386aebc33864e5341 Mon Sep 17 00:00:00 2001
From: Kevin Rocker <me@kevinrocker.com>
Date: Sat, 12 Sep 2026 12:50:55 +0200
Subject: [PATCH 2/2] Offer a daily mbox download on the per-day archive pages

---
 .../mailarchives/templates/datelist.html      |  5 +-
 django/archives/mailarchives/views.py         | 64 +++++++++++++------
 django/archives/urls.py                       |  3 +-
 3 files changed, 51 insertions(+), 21 deletions(-)

diff --git a/django/archives/mailarchives/templates/datelist.html b/django/archives/mailarchives/templates/datelist.html
index df0fb50..7b7ea30 100644
--- a/django/archives/mailarchives/templates/datelist.html
+++ b/django/archives/mailarchives/templates/datelist.html
@@ -11,13 +11,16 @@
 
 {%include "datelist_topandbottom.html"%}
 
+<form id="mail_other_options_form" method="post" action="/"></form>
+
 {%for m in messages%}
   {%ifchanged m.date.date%}
     {%if not forloop.first%}
         </tbody>
       </table>
     {%endif%}
-    <h2>{{m.date.date}}</h2>
+    <h2 class="mb-1">{{m.date.date}}</h2>
+    <div class="text-right"><a href="#" data-ref="/list/{{list.listname}}/mbox/{{list.listname}}.{{m.dateonly}}" class="post-link">Download mbox</a></div>
     <table class="table table-striped table-sm thread-list">
       <thead>
         <tr>
diff --git a/django/archives/mailarchives/views.py b/django/archives/mailarchives/views.py
index c034f90..97a036f 100644
--- a/django/archives/mailarchives/views.py
+++ b/django/archives/mailarchives/views.py
@@ -14,7 +14,7 @@ import copy
 import re
 import os
 import base64
-from datetime import datetime, timedelta, date
+from datetime import datetime, timedelta
 import calendar
 import email.parser
 import email.policy
@@ -744,23 +744,13 @@ def message_mbox(request, msgid):
         msgid, filename='%s.mbox' % quote(msgid, safe=''))
 
 
-@csrf_exempt
-@nocache
-@antispam_auth
-def mbox(request, listname, listname2, mboxyear, mboxmonth):
-    if (listname != listname2):
-        raise Http404('List name mismatch')
-    l = get_object_or_404(List, listname=listname)
-    ensure_list_permissions(request, l)
-
-    mboxyear = int(mboxyear)
-    mboxmonth = int(mboxmonth)
-
-    query = "SELECT messageid, rawtxt FROM messages m INNER JOIN list_threads t ON t.threadid=m.threadid WHERE listid=%(listid)s AND hiddenstatus IS NULL AND date >= %(startdate)s AND date <= %(enddate)s %%% ORDER BY date"
+def _list_mbox(request, l, startdate, enddate, filename):
+    # NOTE! Basic permissions checks must be done before calling this function!
+    query = "SELECT messageid, rawtxt FROM messages m INNER JOIN list_threads t ON t.threadid=m.threadid WHERE listid=%(listid)s AND hiddenstatus IS NULL AND date >= %(startdate)s AND date < %(enddate)s %%% ORDER BY date"
     params = {
         'listid': l.listid,
-        'startdate': date(mboxyear, mboxmonth, 1),
-        'enddate': datetime(mboxyear, mboxmonth, calendar.monthrange(mboxyear, mboxmonth)[1], 23, 59, 59),
+        'startdate': startdate,
+        'enddate': enddate,
     }
 
     if not settings.PUBLIC_ARCHIVES and not request.user.is_superuser:
@@ -770,11 +760,47 @@ def mbox(request, listname, listname2, mboxyear, mboxmonth):
     else:
         # Just return the whole thing
         query = query.replace('%%%', '')
+    return _build_mbox(query, params, filename=filename)
+
+
+@csrf_exempt
+@nocache
+@antispam_auth
+def mbox(request, listname, listname2, mboxyear, mboxmonth):
+    if (listname != listname2):
+        raise Http404('List name mismatch')
+    l = get_object_or_404(List, listname=listname)
+    ensure_list_permissions(request, l)
+
+    try:
+        startdate = datetime(int(mboxyear), int(mboxmonth), 1)
+    except ValueError:
+        raise Http404('Invalid date')
+    enddate = startdate + timedelta(days=calendar.monthrange(startdate.year, startdate.month)[1])
+
     # Set a per-list, per-month filename (matching the URL scheme,
     # listname.YYYYMM) so downloads don't all collide on the same name.
-    return _build_mbox(
-        query, params,
-        filename='%s.%04d%02d.mbox' % (listname, mboxyear, mboxmonth))
+    return _list_mbox(request, l, startdate, enddate,
+                      '%s.%s.mbox' % (listname, startdate.strftime('%Y%m')))
+
+
+@csrf_exempt
+@nocache
+@antispam_auth
+def mbox_day(request, listname, listname2, mboxyear, mboxmonth, mboxday):
+    if (listname != listname2):
+        raise Http404('List name mismatch')
+    l = get_object_or_404(List, listname=listname)
+    ensure_list_permissions(request, l)
+
+    try:
+        startdate = datetime(int(mboxyear), int(mboxmonth), int(mboxday))
+    except ValueError:
+        raise Http404('Invalid date')
+    enddate = startdate + timedelta(days=1)
+
+    return _list_mbox(request, l, startdate, enddate,
+                      '%s.%s.mbox' % (listname, startdate.strftime('%Y%m%d')))
 
 
 @transaction.atomic
diff --git a/django/archives/urls.py b/django/archives/urls.py
index e811b53..cbcb4d8 100644
--- a/django/archives/urls.py
+++ b/django/archives/urls.py
@@ -40,7 +40,8 @@ urlpatterns = [
     re_path(r'^message-id/legacy/([\w-]+)/(\d+)-(\d+)/msg(\d+).php$', archives.mailarchives.views.legacy),
     re_path(r'^message-id/(.+)$', archives.mailarchives.views.message),
 
-    re_path(r'^list/([\w-]+)/mbox/([\w-]+)\.(\d{4})(\d{2})', archives.mailarchives.views.mbox),
+    re_path(r'^list/([\w-]+)/mbox/([\w-]+)\.(\d{4})(\d{2})$', archives.mailarchives.views.mbox),
+    re_path(r'^list/([\w-]+)/mbox/([\w-]+)\.(\d{4})(\d{2})(\d{2})$', archives.mailarchives.views.mbox_day),
 
     # Search
     re_path(r'^archives-search/', archives.mailarchives.views.search),
-- 
2.54.0

