#! @PYTHON@
# Copyright (C) 2001-2018 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
"""Clean up an .mbox archive file.
The archiver looks for Unix-From lines separating messages in an mbox archive
file. For compatibility, it specifically looks for lines that start with
"From " -- i.e. the letters capital-F, lowercase-r, o, m, space, ignoring
everything else on the line.
Normally, any lines that start "From " in the body of a message should be
escaped such that a > character is actually the first on a line. It is
possible though that body lines are not actually escaped. This script
attempts to fix these by doing a stricter test of the Unix-From lines. Any
lines that start "From " but do not pass this stricter test are escaped with a
> character.
Usage: cleanarch [options] < inputfile > outputfile
Options:
-s n
--status=n
Print a # character every n lines processed
-q / --quiet
Don't print changed line information to standard error.
-n / --dry-run
Don't actually output anything.
-h / --help
Print this message and exit
"""
import re
import sys
import getopt
import mailbox
import paths
from Mailman.i18n import C_
cre = re.compile(mailbox.UnixMailbox._fromlinepattern)
# From RFC 2822, a header field name must contain only characters from 33-126
# inclusive, excluding colon. I.e. from oct 41 to oct 176 less oct 072. Must
# use re.match() so that it's anchored at the beginning of the line.
fre = re.compile(r'[\041-\071\073-\176]+')
def usage(code, msg=''):
if code:
fd = sys.stderr
else:
fd = sys.stdout
print >> fd, C_(__doc__)
if msg:
print >> fd, msg
sys.exit(code)
def escape_line(line, lineno, quiet, output):
if output:
sys.stdout.write('>' + line)
if not quiet:
print >> sys.stderr, C_('Unix-From line changed: %(lineno)d')
print >> sys.stderr, line[:-1]
def main():
try:
opts, args = getopt.getopt(
sys.argv[1:], 'hqns:',
['help', 'quiet', 'dry-run', 'status='])
except getopt.error, msg:
usage(1, msg)
quiet = False
output = True
status = -1
for opt, arg in opts:
if opt in ('-h', '--help'):
usage(0)
elif opt in ('-q', '--quiet'):
quiet = True
elif opt in ('-n', '--dry-run'):
output = False
elif opt in ('-s', '--status'):
try:
status = int(arg)
except ValueError:
usage(1, C_('Bad status number: %(arg)s'))
if args:
usage(1)
lineno = 0
statuscnt = 0
messages = 0
prevline = None
while True:
lineno += 1
line = sys.stdin.readline()
if not line:
break
if line.startswith('From '):
if cre.match(line):
# This is a real Unix-From line. But it could be a message
# /about/ Unix-From lines, so as a second order test, make
# sure there's at least one RFC 2822 header following
nextline = sys.stdin.readline()
lineno += 1
if not nextline:
# It was the last line of the mbox, so it couldn't have
# been a Unix-From
escape_line(line, lineno, quiet, output)
break
fieldname = nextline.split(':', 1)
if len(fieldname) < 2 or not fre.match(nextline):
# The following line was not a header, so this wasn't a
# valid Unix-From
escape_line(line, lineno, quiet, output)
if output:
sys.stdout.write(nextline)
else:
# It's a valid Unix-From line
messages += 1
if output:
# Before we spit out the From_ line, make sure the
# previous line was blank.
if prevline is not None and prevline <> '\n':
sys.stdout.write('\n')
sys.stdout.write(line)
sys.stdout.write(nextline)
else:
# This is a bogus Unix-From line
escape_line(line, lineno, quiet, output)
elif output:
# Any old line
sys.stdout.write(line)
if status > 0 and (lineno % status) == 0:
sys.stderr.write('#')
statuscnt += 1
if statuscnt > 50:
print >> sys.stderr
statuscnt = 0
prevline = line
print >> sys.stderr, C_('%(messages)d messages found')
if __name__ == '__main__':
main()