Skip to content

Commit 637a74b

Browse files
jared mauchjared mauch
authored andcommitted
Major Python 3 migration and bug fixes
This commit consolidates extensive Python 3 compatibility improvements, bug fixes, and feature enhancements including: - Python 3 migration: Replace raw_input with input, fix execfile usage, handle string/bytes encoding throughout codebase - Encoding fixes: UTF-8 handling, proper encoding for email headers, subscription forms, and message decoration - Archiver improvements: Fix on-the-fly archiving, handle non-ASCII characters, improve archive format handling - cPanel integration: Sync fixes for pickle protocol detection, encoding issues, and various CPANEL ticket fixes - Configuration: Improve mm_cfg handler, fix circular import issues - Bug fixes: Fix TypeError with string encoding/hashing, KeyError in HyperDatabase, NNTP bridge issues, and various other fixes - Cron script fixes: Update senddigests, checkdbs, and other cron jobs for Python 3 compatibility This represents a comprehensive update bringing the codebase to full Python 3 compatibility while maintaining backward compatibility where possible.
1 parent fc68847 commit 637a74b

1,139 files changed

Lines changed: 56769 additions & 115434 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Mailman/Archiver/Archiver.py

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import errno
3030
import traceback
3131
import re
32-
from io import StringIO
32+
import tempfile
3333

3434
from Mailman import mm_cfg
3535
from Mailman import Mailbox
@@ -88,17 +88,21 @@ def InitVars(self):
8888
# symbolic links.
8989
omask = os.umask(0)
9090
try:
91-
# Create mbox directory with proper permissions
92-
mbox_dir = self.archive_dir() + '.mbox'
93-
os.makedirs(mbox_dir, mode=0o02775, exist_ok=True)
94-
95-
# Create archive directory with proper permissions
96-
archive_dir = self.archive_dir()
97-
os.makedirs(archive_dir, mode=0o02775, exist_ok=True)
98-
91+
try:
92+
os.mkdir(self.archive_dir()+'.mbox', 0o02775)
93+
except OSError as e:
94+
if e.errno != errno.EEXIST: raise
95+
# We also create an empty pipermail archive directory into
96+
# which we'll drop an empty index.html file into. This is so
97+
# that lists that have not yet received a posting have
98+
# /something/ as their index.html, and don't just get a 404.
99+
try:
100+
os.mkdir(self.archive_dir(), 0o02775)
101+
except OSError as e:
102+
if e.errno != errno.EEXIST: raise
99103
# See if there's an index.html file there already and if not,
100104
# write in the empty archive notice.
101-
indexfile = os.path.join(archive_dir, 'index.html')
105+
indexfile = os.path.join(self.archive_dir(), 'index.html')
102106
fp = None
103107
try:
104108
fp = open(indexfile)
@@ -132,7 +136,8 @@ def GetBaseArchiveURL(self):
132136
if self.archive_private:
133137
return url
134138
else:
135-
hostname = re.match(r'[^:]*://([^/]*)/.*', url, re.IGNORECASE).group(1)
139+
hostname = re.match('[^:]*://([^/]*)/.*', url).group(1)\
140+
or mm_cfg.DEFAULT_URL_HOST
136141
url = mm_cfg.PUBLIC_ARCHIVE_URL % {
137142
'listname': self.internal_name(),
138143
'hostname': hostname
@@ -145,7 +150,7 @@ def __archive_file(self, afn):
145150
"""Open (creating, if necessary) the named archive file."""
146151
omask = os.umask(0o002)
147152
try:
148-
return Mailbox.Mailbox(open(afn, 'a+'))
153+
return Mailbox.Mailbox(open(afn, 'a+b'))
149154
finally:
150155
os.umask(omask)
151156

@@ -157,9 +162,11 @@ def __archive_to_mbox(self, post):
157162
"""Retain a text copy of the message in an mbox file."""
158163
try:
159164
afn = self.ArchiveFileName()
165+
syslog('debug', 'Archiver: Writing to mbox file: %s', afn)
160166
mbox = self.__archive_file(afn)
161167
mbox.AppendMessage(post)
162-
mbox.fp.close()
168+
mbox.close()
169+
syslog('debug', 'Archiver: Successfully wrote message to mbox file: %s', afn)
163170
except IOError as msg:
164171
syslog('error', 'Archive file access failure:\n\t%s %s', afn, msg)
165172
raise
@@ -169,55 +176,68 @@ def ExternalArchive(self, ar, txt):
169176
'hostname': self.host_name,
170177
})
171178
cmd = ar % d
172-
try:
173-
with os.popen(cmd, 'w') as extarch:
174-
extarch.write(txt)
175-
except OSError as e:
176-
syslog('error', 'Failed to execute external archiver: %s\nError: %s',
177-
cmd, str(e))
178-
return
179+
extarch = os.popen(cmd, 'w')
180+
extarch.write(txt)
179181
status = extarch.close()
180182
if status:
181-
syslog('error', 'External archiver non-zero exit status: %d\nCommand: %s',
182-
(status & 0xff00) >> 8, cmd)
183+
syslog('error', 'external archiver non-zero exit status: %d\n',
184+
(status & 0xff00) >> 8)
183185

184186
#
185187
# archiving in real time this is called from list.post(msg)
186188
#
187189
def ArchiveMail(self, msg):
188190
"""Store postings in mbox and/or pipermail archive, depending."""
191+
from Mailman.Logging.Syslog import syslog
192+
syslog('debug', 'Archiver: Starting ArchiveMail for list %s', self.internal_name())
193+
189194
# Fork so archival errors won't disrupt normal list delivery
190195
if mm_cfg.ARCHIVE_TO_MBOX == -1:
196+
syslog('debug', 'Archiver: ARCHIVE_TO_MBOX is -1, archiving disabled')
191197
return
198+
199+
syslog('debug', 'Archiver: ARCHIVE_TO_MBOX = %s', mm_cfg.ARCHIVE_TO_MBOX)
192200
#
193201
# We don't need an extra archiver lock here because we know the list
194202
# itself must be locked.
195203
if mm_cfg.ARCHIVE_TO_MBOX in (1, 2):
196-
try:
197-
mbox = self.__archive_file(self.ArchiveFileName())
198-
mbox.AppendMessage(msg)
199-
mbox.fp.close()
200-
except IOError as msg:
201-
syslog('error', 'Archive file access failure:\n\t%s %s',
202-
self.ArchiveFileName(), msg)
203-
raise
204+
syslog('debug', 'Archiver: Writing to mbox archive')
205+
self.__archive_to_mbox(msg)
204206
if mm_cfg.ARCHIVE_TO_MBOX == 1:
205207
# Archive to mbox only.
208+
syslog('debug', 'Archiver: ARCHIVE_TO_MBOX = 1, mbox only, returning')
206209
return
207-
txt = str(msg)
210+
211+
txt = msg.as_string()
212+
unixfrom = msg.get_unixfrom()
213+
# Handle case where unixfrom is None (Python 3 compatibility)
214+
if unixfrom and not txt.startswith(unixfrom):
215+
txt = unixfrom + '\n' + txt
216+
208217
# should we use the internal or external archiver?
209218
private_p = self.archive_private
219+
syslog('debug', 'Archiver: archive_private = %s', private_p)
220+
210221
if mm_cfg.PUBLIC_EXTERNAL_ARCHIVER and not private_p:
222+
syslog('debug', 'Archiver: Using public external archiver')
211223
self.ExternalArchive(mm_cfg.PUBLIC_EXTERNAL_ARCHIVER, txt)
212224
elif mm_cfg.PRIVATE_EXTERNAL_ARCHIVER and private_p:
225+
syslog('debug', 'Archiver: Using private external archiver')
213226
self.ExternalArchive(mm_cfg.PRIVATE_EXTERNAL_ARCHIVER, txt)
214227
else:
215228
# use the internal archiver
216-
with StringIO(txt) as f:
217-
from . import HyperArch
218-
h = HyperArch.HyperArchive(self)
219-
h.processUnixMailbox(f)
220-
h.close()
229+
syslog('debug', 'Archiver: Using internal HyperArch archiver')
230+
f = tempfile.NamedTemporaryFile()
231+
if isinstance(txt, str):
232+
txt = txt.encode('utf-8')
233+
f.write(txt)
234+
f.flush()
235+
from . import HyperArch
236+
h = HyperArch.HyperArchive(self)
237+
h.processUnixMailbox(f)
238+
h.close()
239+
f.close()
240+
syslog('debug', 'Archiver: Completed internal archiving')
221241

222242
#
223243
# called from MailList.MailList.Save()

0 commit comments

Comments
 (0)