summaryrefslogtreecommitdiff
path: root/Mailman/versions.py
diff options
context:
space:
mode:
Diffstat (limited to 'Mailman/versions.py')
-rw-r--r--Mailman/versions.py517
1 files changed, 0 insertions, 517 deletions
diff --git a/Mailman/versions.py b/Mailman/versions.py
deleted file mode 100644
index 8052db346..000000000
--- a/Mailman/versions.py
+++ /dev/null
@@ -1,517 +0,0 @@
-# Copyright (C) 1998-2007 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.
-
-
-"""Routines which rectify an old mailing list with current structure.
-
-The MailList.CheckVersion() method looks for an old .data_version setting in
-the loaded structure, and if found calls the Update() routine from this
-module, supplying the list and the state last loaded from storage. The state
-is necessary to distinguish from default assignments done in the .InitVars()
-methods, before .CheckVersion() is called.
-
-For new versions you should add sections to the UpdateOldVars() and the
-UpdateOldUsers() sections, to preserve the sense of settings across structural
-changes. Note that the routines have only one pass - when .CheckVersions()
-finds a version change it runs this routine and then updates the data_version
-number of the list, and then does a .Save(), so the transformations won't be
-run again until another version change is detected.
-"""
-
-import email
-import logging
-
-from Mailman import Message
-from Mailman import Utils
-from Mailman.MemberAdaptor import UNKNOWN
-from Mailman.configuration import config
-
-log = logging.getLogger('mailman.error')
-
-
-
-def Update(l, stored_state):
- "Dispose of old vars and user options, mapping to new ones when suitable."
- ZapOldVars(l)
- UpdateOldUsers(l)
- NewVars(l)
- UpdateOldVars(l, stored_state)
- CanonicalizeUserOptions(l)
- NewRequestsDatabase(l)
-
-
-
-def ZapOldVars(mlist):
- for name in ('num_spawns', 'filter_prog', 'clobber_date',
- 'public_archive_file_dir', 'private_archive_file_dir',
- 'archive_directory',
- # Pre-2.1a4 bounce data
- 'minimum_removal_date',
- 'minimum_post_count_before_bounce_action',
- 'automatic_bounce_action',
- 'max_posts_between_bounces',
- ):
- if hasattr(mlist, name):
- delattr(mlist, name)
-
-
-
-uniqueval = []
-def UpdateOldVars(l, stored_state):
- """Transform old variable values into new ones, deleting old ones.
- stored_state is last snapshot from file, as opposed to from InitVars()."""
-
- def PreferStored(oldname, newname, newdefault=uniqueval,
- l=l, state=stored_state):
- """Use specified old value if new value is not in stored state.
-
- If the old attr does not exist, and no newdefault is specified, the
- new attr is *not* created - so either specify a default or be positive
- that the old attr exists - or don't depend on the new attr.
-
- """
- if hasattr(l, oldname):
- if not state.has_key(newname):
- setattr(l, newname, getattr(l, oldname))
- delattr(l, oldname)
- if not hasattr(l, newname) and newdefault is not uniqueval:
- setattr(l, newname, newdefault)
-
- # Migrate to 2.1b3, baw 17-Aug-2001
- if hasattr(l, 'dont_respond_to_post_requests'):
- oldval = getattr(l, 'dont_respond_to_post_requests')
- if not hasattr(l, 'respond_to_post_requests'):
- l.respond_to_post_requests = not oldval
- del l.dont_respond_to_post_requests
-
- # Migrate to 2.1b3, baw 13-Oct-2001
- # Basic defaults for new variables
- if not hasattr(l, 'default_member_moderation'):
- l.default_member_moderation = config.DEFAULT_DEFAULT_MEMBER_MODERATION
- if not hasattr(l, 'accept_these_nonmembers'):
- l.accept_these_nonmembers = []
- if not hasattr(l, 'hold_these_nonmembers'):
- l.hold_these_nonmembers = []
- if not hasattr(l, 'reject_these_nonmembers'):
- l.reject_these_nonmembers = []
- if not hasattr(l, 'discard_these_nonmembers'):
- l.discard_these_nonmembers = []
- if not hasattr(l, 'forward_auto_discards'):
- l.forward_auto_discards = config.DEFAULT_FORWARD_AUTO_DISCARDS
- if not hasattr(l, 'generic_nonmember_action'):
- l.generic_nonmember_action = config.DEFAULT_GENERIC_NONMEMBER_ACTION
- # Now convert what we can... Note that the interaction between the
- # MM2.0.x attributes `moderated', `member_posting_only', and `posters' is
- # so confusing, it makes my brain really ache. Which is why they go away
- # in MM2.1. I think the best we can do semantically is the following:
- #
- # - If moderated == yes, then any sender who's address is not on the
- # posters attribute would get held for approval. If the sender was on
- # the posters list, then we'd defer judgement to a later step
- # - If member_posting_only == yes, then members could post without holds,
- # and if there were any addresses added to posters, they could also post
- # without holds.
- # - If member_posting_only == no, then what happens depends on the value
- # of the posters attribute:
- # o If posters was empty, then anybody can post without their
- # message being held for approval
- # o If posters was non-empty, then /only/ those addresses could post
- # without approval, i.e. members not on posters would have their
- # messages held for approval.
- #
- # How to translate this mess to MM2.1 values? I'm sure I got this wrong
- # before, but here's how we're going to do it, as of MM2.1b3.
- #
- # - We'll control member moderation through their Moderate flag, and
- # non-member moderation through the generic_nonmember_action,
- # hold_these_nonmembers, and accept_these_nonmembers.
- # - If moderated == yes then we need to troll through the addresses on
- # posters, and any non-members would get added to
- # accept_these_nonmembers. /Then/ we need to troll through the
- # membership and any member on posters would get their Moderate flag
- # unset, while members not on posters would get their Moderate flag set.
- # Then generic_nonmember_action gets set to 1 (hold) so nonmembers get
- # moderated, and default_member_moderation will be set to 1 (hold) so
- # new members will also get held for moderation. We'll stop here.
- # - We only get to here if moderated == no.
- # - If member_posting_only == yes, then we'll turn off the Moderate flag
- # for members. We troll through the posters attribute and add all those
- # addresses to accept_these_nonmembers. We'll also set
- # generic_nonmember_action to 1 and default_member_moderation to 0.
- # We'll stop here.
- # - We only get to here if member_posting_only == no
- # - If posters is empty, then anybody could post without being held for
- # approval, so we'll set generic_nonmember_action to 0 (accept), and
- # we'll turn off the Moderate flag for all members. We'll also turn off
- # default_member_moderation so new members can post without approval.
- # We'll stop here.
- # - We only get here if posters is non-empty.
- # - This means that /only/ the addresses on posters got to post without
- # being held for approval. So first, we troll through posters and add
- # all non-members to accept_these_nonmembers. Then we troll through the
- # membership and if their address is on posters, we'll clear their
- # Moderate flag, otherwise we'll set it. We'll turn on
- # default_member_moderation so new members get moderated. We'll set
- # generic_nonmember_action to 1 (hold) so all other non-members will get
- # moderated. And I think we're finally done.
- #
- # SIGH.
- if hasattr(l, 'moderated'):
- # We'll assume we're converting all these attributes at once
- if l.moderated:
- for addr in l.posters:
- if not l.isMember(addr):
- l.accept_these_nonmembers.append(addr)
- for member in l.getMembers():
- l.setMemberOption(member, config.Moderate,
- # reset for explicitly named members
- member not in l.posters)
- l.generic_nonmember_action = 1
- l.default_member_moderation = 1
- elif l.member_posting_only:
- for addr in l.posters:
- if not l.isMember(addr):
- l.accept_these_nonmembers.append(addr)
- for member in l.getMembers():
- l.setMemberOption(member, config.Moderate, 0)
- l.generic_nonmember_action = 1
- l.default_member_moderation = 0
- elif not l.posters:
- for member in l.getMembers():
- l.setMemberOption(member, config.Moderate, 0)
- l.generic_nonmember_action = 0
- l.default_member_moderation = 0
- else:
- for addr in l.posters:
- if not l.isMember(addr):
- l.accept_these_nonmembers.append(addr)
- for member in l.getMembers():
- l.setMemberOption(member, config.Moderate,
- # reset for explicitly named members
- member not in l.posters)
- l.generic_nonmember_action = 1
- l.default_member_moderation = 1
- # Now get rid of the old attributes
- del l.moderated
- del l.posters
- del l.member_posting_only
- if hasattr(l, 'forbidden_posters'):
- # For each of the posters on this list, if they are members, toggle on
- # their moderation flag. If they are not members, then add them to
- # hold_these_nonmembers.
- forbiddens = l.forbidden_posters
- for addr in forbiddens:
- if l.isMember(addr):
- l.setMemberOption(addr, config.Moderate, 1)
- else:
- l.hold_these_nonmembers.append(addr)
- del l.forbidden_posters
-
- # Migrate to 1.0b6, klm 10/22/1998:
- PreferStored('reminders_to_admins', 'umbrella_list',
- config.DEFAULT_UMBRELLA_LIST)
-
- # Migrate up to 1.0b5:
- PreferStored('auto_subscribe', 'open_subscribe')
- PreferStored('closed', 'private_roster')
- PreferStored('mimimum_post_count_before_removal',
- 'mimimum_post_count_before_bounce_action')
- PreferStored('bad_posters', 'forbidden_posters')
- PreferStored('automatically_remove', 'automatic_bounce_action')
- if hasattr(l, "open_subscribe"):
- if l.open_subscribe:
- if config.ALLOW_OPEN_SUBSCRIBE:
- l.subscribe_policy = 0
- else:
- l.subscribe_policy = 1
- else:
- l.subscribe_policy = 2 # admin approval
- delattr(l, "open_subscribe")
- if not hasattr(l, "administrivia"):
- setattr(l, "administrivia", config.DEFAULT_ADMINISTRIVIA)
- if not hasattr(l, "admin_member_chunksize"):
- setattr(l, "admin_member_chunksize",
- config.DEFAULT_ADMIN_MEMBER_CHUNKSIZE)
- #
- # this attribute was added then deleted, so there are a number of
- # cases to take care of
- #
- if hasattr(l, "posters_includes_members"):
- if l.posters_includes_members:
- if l.posters:
- l.member_posting_only = 1
- else:
- if l.posters:
- l.member_posting_only = 0
- delattr(l, "posters_includes_members")
- elif l.data_version <= 10 and l.posters:
- # make sure everyone gets the behavior the list used to have, but only
- # for really old versions of Mailman (1.0b5 or before). Any newer
- # version of Mailman should not get this attribute whacked.
- l.member_posting_only = 0
- #
- # transfer the list data type for holding members and digest members
- # to the dict data type starting file format version 11
- #
- if isinstance(l.members, list):
- members = {}
- for m in l.members:
- members[m] = 1
- l.members = members
- if isinstance(l.digest_members, list):
- dmembers = {}
- for dm in l.digest_members:
- dmembers[dm] = 1
- l.digest_members = dmembers
- #
- # set admin_notify_mchanges
- #
- if not hasattr(l, "admin_notify_mchanges"):
- setattr(l, "admin_notify_mchanges",
- config.DEFAULT_ADMIN_NOTIFY_MCHANGES)
- #
- # Convert the members and digest_members addresses so that the keys of
- # both these are always lowercased, but if there is a case difference, the
- # value contains the case preserved value
- #
- for k in l.members.keys():
- if k.lower() <> k:
- l.members[k.lower()] = Utils.LCDomain(k)
- del l.members[k]
- elif isinstance(l.members[k], str) and k == l.members[k].lower():
- # already converted
- pass
- else:
- l.members[k] = 0
- for k in l.digest_members.keys():
- if k.lower() <> k:
- l.digest_members[k.lower()] = Utils.LCDomain(k)
- del l.digest_members[k]
- elif isinstance(l.digest_members[k], str) and \
- k == l.digest_members[k].lower():
- # already converted
- pass
- else:
- l.digest_members[k] = 0
- #
- # Convert pre 2.2 topics regexps which were compiled in verbose mode
- # to a non-verbose equivalent.
- #
- if stored_state['data_version'] <= 97 and stored_state.has_key('topics'):
- l.topics = []
- for name, pattern, description, emptyflag in stored_state['topics']:
- pattern = Utils.strip_verbose_pattern(pattern)
- l.topics.append((name, pattern, description, emptyflag))
-
-
-
-def NewVars(l):
- """Add defaults for these new variables if they don't exist."""
- def add_only_if_missing(attr, initval, l=l):
- if not hasattr(l, attr):
- setattr(l, attr, initval)
- # 1.2 beta 1, baw 18-Feb-2000
- # Autoresponder mixin class attributes
- add_only_if_missing('autorespond_postings', 0)
- add_only_if_missing('autorespond_admin', 0)
- add_only_if_missing('autorespond_requests', 0)
- add_only_if_missing('autoresponse_postings_text', '')
- add_only_if_missing('autoresponse_admin_text', '')
- add_only_if_missing('autoresponse_request_text', '')
- add_only_if_missing('autoresponse_graceperiod', 90)
- add_only_if_missing('postings_responses', {})
- add_only_if_missing('admin_responses', {})
- add_only_if_missing('reply_goes_to_list', '')
- add_only_if_missing('preferred_language', config.DEFAULT_SERVER_LANGUAGE)
- add_only_if_missing('available_languages', [])
- add_only_if_missing('digest_volume_frequency',
- config.DEFAULT_DIGEST_VOLUME_FREQUENCY)
- add_only_if_missing('digest_last_sent_at', 0)
- add_only_if_missing('mod_password', None)
- add_only_if_missing('moderator', [])
- add_only_if_missing('topics', [])
- add_only_if_missing('topics_enabled', 0)
- add_only_if_missing('topics_bodylines_limit', 5)
- add_only_if_missing('one_last_digest', {})
- add_only_if_missing('usernames', {})
- add_only_if_missing('personalize', 0)
- add_only_if_missing('first_strip_reply_to',
- config.DEFAULT_FIRST_STRIP_REPLY_TO)
- add_only_if_missing('subscribe_auto_approval',
- config.DEFAULT_SUBSCRIBE_AUTO_APPROVAL)
- add_only_if_missing('unsubscribe_policy',
- config.DEFAULT_UNSUBSCRIBE_POLICY)
- add_only_if_missing('send_goodbye_msg', config.DEFAULT_SEND_GOODBYE_MSG)
- add_only_if_missing('include_rfc2369_headers', 1)
- add_only_if_missing('include_list_post_header', 1)
- add_only_if_missing('bounce_score_threshold',
- config.DEFAULT_BOUNCE_SCORE_THRESHOLD)
- add_only_if_missing('bounce_info_stale_after',
- config.DEFAULT_BOUNCE_INFO_STALE_AFTER)
- add_only_if_missing('bounce_you_are_disabled_warnings',
- config.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS)
- add_only_if_missing(
- 'bounce_you_are_disabled_warnings_interval',
- config.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL)
- add_only_if_missing(
- 'bounce_unrecognized_goes_to_list_owner',
- config.DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER)
- add_only_if_missing(
- 'bounce_notify_owner_on_disable',
- config.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE)
- add_only_if_missing(
- 'bounce_notify_owner_on_removal',
- config.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL)
- add_only_if_missing('ban_list', [])
- add_only_if_missing('filter_mime_types', config.DEFAULT_FILTER_MIME_TYPES)
- add_only_if_missing('pass_mime_types', config.DEFAULT_PASS_MIME_TYPES)
- add_only_if_missing('filter_content', config.DEFAULT_FILTER_CONTENT)
- add_only_if_missing('convert_html_to_plaintext',
- config.DEFAULT_CONVERT_HTML_TO_PLAINTEXT)
- add_only_if_missing('filter_action', config.DEFAULT_FILTER_ACTION)
- add_only_if_missing('delivery_status', {})
- # This really ought to default to config.HOLD, but that doesn't work with
- # the current GUI description model. So, 0==Hold, 1==Reject, 2==Discard
- add_only_if_missing('member_moderation_action', 0)
- add_only_if_missing('member_moderation_notice', '')
- add_only_if_missing('new_member_options',
- config.DEFAULT_NEW_MEMBER_OPTIONS)
- # Emergency moderation flag
- add_only_if_missing('emergency', 0)
- add_only_if_missing('hold_and_cmd_autoresponses', {})
- add_only_if_missing('news_prefix_subject_too', 1)
- # Should prefixes be encoded?
- if Utils.GetCharSet(l.preferred_language) == 'us-ascii':
- encode = 0
- else:
- encode = 2
- add_only_if_missing('encode_ascii_prefixes', encode)
- add_only_if_missing('news_moderation', 0)
- add_only_if_missing('header_filter_rules', [])
- # Scrubber in regular delivery
- add_only_if_missing('scrub_nondigest', 0)
- # ContentFilter by file extensions
- add_only_if_missing('filter_filename_extensions',
- config.DEFAULT_FILTER_FILENAME_EXTENSIONS)
- add_only_if_missing('pass_filename_extensions', [])
- # automatic discard
- add_only_if_missing('max_days_to_hold', 0)
- add_only_if_missing('nonmember_rejection_notice', '')
- # multipart/alternative collapse
- add_only_if_missing('collapse_alternatives',
- config.DEFAULT_COLLAPSE_ALTERNATIVES)
-
-
-
-def UpdateOldUsers(mlist):
- """Transform sense of changed user options."""
- # pre-1.0b11 to 1.0b11. Force all keys in l.passwords to be lowercase
- passwords = {}
- for k, v in mlist.passwords.items():
- passwords[k.lower()] = v
- mlist.passwords = passwords
- # Go through all the keys in bounce_info. If the key is not a member, or
- # if the data is not a _BounceInfo instance, chuck the bounce info. We're
- # doing things differently now.
- from Mailman.Bouncer import _BounceInfo
- for m in mlist.bounce_info.keys():
- if not mlist.isMember(m) or not isinstance(mlist.getBounceInfo(m),
- _BounceInfo):
- del mlist.bounce_info[m]
-
-
-
-def CanonicalizeUserOptions(l):
- """Fix up the user options."""
- # I want to put a flag in the list database which tells this routine to
- # never try to canonicalize the user options again.
- if getattr(l, 'useropts_version', 0) > 0:
- return
- # pre 1.0rc2 to 1.0rc3. For all keys in l.user_options to be lowercase,
- # but merge options for both cases
- options = {}
- for k, v in l.user_options.items():
- if k is None:
- continue
- lcuser = k.lower()
- flags = 0
- if options.has_key(lcuser):
- flags = options[lcuser]
- flags |= v
- options[lcuser] = flags
- l.user_options = options
- # 2.1alpha3 -> 2.1alpha4. The DisableDelivery flag is now moved into
- # get/setDeilveryStatus(). This must be done after the addresses are
- # canonicalized.
- for k, v in l.user_options.items():
- if not l.isMember(k):
- # There's a key in user_options that isn't associated with a real
- # member address. This is likely caused by an earlier bug.
- del l.user_options[k]
- continue
- if l.getMemberOption(k, config.DisableDelivery):
- # Convert this flag into a legacy disable
- l.setDeliveryStatus(k, UNKNOWN)
- l.setMemberOption(k, config.DisableDelivery, 0)
- l.useropts_version = 1
-
-
-
-def NewRequestsDatabase(l):
- """With version 1.2, we use a new pending request database schema."""
- r = getattr(l, 'requests', {})
- if not r:
- # no old-style requests
- return
- for k, v in r.items():
- if k == 'post':
- # This is a list of tuples with the following format
- #
- # a sequential request id integer
- # a timestamp float
- # a message tuple: (author-email-str, message-text-str)
- # a reason string
- # the subject string
- #
- # We'll re-submit this as a new HoldMessage request, but we'll
- # blow away the original timestamp and request id. This means the
- # request will live a little longer than it possibly should have,
- # but that's no big deal.
- for p in v:
- author, text = p[2]
- reason = p[3]
- msg = email.message_from_string(text, Message.Message)
- l.HoldMessage(msg, reason)
- del r[k]
- elif k == 'add_member':
- # This is a list of tuples with the following format
- #
- # a sequential request id integer
- # a timestamp float
- # a digest flag (0 == nodigest, 1 == digest)
- # author-email-str
- # password
- #
- # See the note above; the same holds true.
- for ign, ign, digest, addr, password in v:
- l.HoldSubscription(addr, '', password, digest,
- config.DEFAULT_SERVER_LANGUAGE)
- del r[k]
- else:
- log.error("""\
-VERY BAD NEWS. Unknown pending request type `%s' found for list: %s""",
- k, l.internal_name())