1 # Copyright (C) 2007-2014 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """Implementations of the IPendable and IPending interfaces."""
20 from __future__
import absolute_import
, print_function
, unicode_literals
33 from lazr
.config
import as_timedelta
34 from storm
.locals import DateTime
, Int
, RawStr
, ReferenceSet
, Unicode
35 from zope
.interface
import implementer
36 from zope
.interface
.verify
import verifyObject
38 from mailman
.config
import config
39 from mailman
.database
.model
import Model
40 from mailman
.database
.transaction
import dbconnection
41 from mailman
.interfaces
.pending
import (
42 IPendable
, IPended
, IPendedKeyValue
, IPendings
)
43 from mailman
.utilities
.datetime
import now
44 from mailman
.utilities
.modules
import call_name
48 @implementer(IPendedKeyValue
)
49 class PendedKeyValue(Model
):
50 """A pended key/value pair, tied to a token."""
52 def __init__(self
, key
, value
):
56 id = Int(primary
=True)
65 """A pended event, tied to a token."""
67 def __init__(self
, token
, expiration_date
):
68 super(Pended
, self
).__init
__()
70 self
.expiration_date
= expiration_date
72 id = Int(primary
=True)
74 expiration_date
= DateTime()
75 key_values
= ReferenceSet(id, PendedKeyValue
.pended_id
)
79 @implementer(IPendable
)
80 class UnpendedPendable(dict):
85 @implementer(IPendings
)
87 """Implementation of the IPending interface."""
90 def add(self
, store
, pendable
, lifetime
=None):
91 verifyObject(IPendable
, pendable
)
92 # Calculate the token and the lifetime.
94 lifetime
= as_timedelta(config
.mailman
.pending_request_life
)
95 # Calculate a unique token. Algorithm vetted by the Timbot. time()
96 # has high resolution on Linux, clock() on Windows. random gives us
97 # about 45 bits in Python 2.2, 53 bits on Python 2.3. The time and
98 # clock values basically help obscure the random number generator, as
99 # does the hash calculation. The integral parts of the time values
100 # are discarded because they're the most predictable bits.
101 for attempts
in range(3):
102 right_now
= time
.time()
103 x
= random
.random() + right_now
% 1.0 + time
.clock() % 1.0
104 # Use sha1 because it produces shorter strings.
105 token
= hashlib
.sha1(repr(x
)).hexdigest()
106 # In practice, we'll never get a duplicate, but we'll be anal
107 # about checking anyway.
108 if store
.find(Pended
, token
=token
).count() == 0:
111 raise AssertionError('Could not find a valid pendings token')
112 # Create the record, and then the individual key/value pairs.
115 expiration_date
=now() + lifetime
)
116 for key
, value
in pendable
.items():
117 if isinstance(key
, str):
118 key
= unicode(key
, 'utf-8')
119 if isinstance(value
, str):
120 value
= unicode(value
, 'utf-8')
121 elif type(value
) is int:
122 value
= '__builtin__.int\1%s' % value
123 elif type(value
) is float:
124 value
= '__builtin__.float\1%s' % value
125 elif type(value
) is bool:
126 value
= '__builtin__.bool\1%s' % value
127 elif type(value
) is list:
128 # We expect this to be a list of strings.
129 value
= ('mailman.model.pending.unpack_list\1' +
131 keyval
= PendedKeyValue(key
=key
, value
=value
)
132 pending
.key_values
.add(keyval
)
137 def confirm(self
, store
, token
, expunge
=True):
138 # Token can come in as a unicode, but it's stored in the database as
139 # bytes. They must be ascii.
140 pendings
= store
.find(Pended
, token
=str(token
))
141 if pendings
.count() == 0:
143 assert pendings
.count() == 1, (
144 'Unexpected token count: {0}'.format(pendings
.count()))
145 pending
= pendings
[0]
146 pendable
= UnpendedPendable()
147 # Find all PendedKeyValue entries that are associated with the pending
148 # object's ID. Watch out for type conversions.
149 for keyvalue
in store
.find(PendedKeyValue
,
150 PendedKeyValue
.pended_id
== pending
.id):
151 if keyvalue
.value
is not None and '\1' in keyvalue
.value
:
152 type_name
, value
= keyvalue
.value
.split('\1', 1)
153 pendable
[keyvalue
.key
] = call_name(type_name
, value
)
155 pendable
[keyvalue
.key
] = keyvalue
.value
157 store
.remove(keyvalue
)
159 store
.remove(pending
)
163 def evict(self
, store
):
165 for pending
in store
.find(Pended
):
166 if pending
.expiration_date
< right_now
:
167 # Find all PendedKeyValue entries that are associated with the
168 # pending object's ID.
169 q
= store
.find(PendedKeyValue
,
170 PendedKeyValue
.pended_id
== pending
.id)
172 store
.remove(keyvalue
)
173 store
.remove(pending
)
177 def unpack_list(value
):
178 return value
.split('\2')