Skip to content

Commit 4af8dfe

Browse files
author
grahamd
committed
(MODPYTHON-191) Session class will no longer accept a normal cookie if a
signed cookie was expected. (MODPYTHON-200) Fixed problem whereby signed and marshalled cookies could not be used at the same time. When expecting marshalled cookie, any signed, but not marshalled cookies will be returned as normal cookies.
1 parent ef9cc84 commit 4af8dfe

9 files changed

Lines changed: 133 additions & 50 deletions

File tree

Doc/appendixc.tex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,10 @@ \chapter{Changes from Version (3.2.10)\label{app-changes-from-3.2.10}}
450450
\item
451451
(\citetitle[http://issues.apache.org/jira/browse/MODPYTHON-189]{MODPYTHON-189})
452452
Fixed representation returned by calling \code{repr()} on a table object.
453+
\item
454+
(\citetitle[http://issues.apache.org/jira/browse/MODPYTHON-191]{MODPYTHON-191})
455+
Session class will no longer accept a normal cookie if a signed cookie
456+
was expected.
453457
\item
454458
(\citetitle[http://issues.apache.org/jira/browse/MODPYTHON-194]{MODPYTHON-194})
455459
Fixed potential memory leak due to not clearing the state of thread state
@@ -458,6 +462,11 @@ \chapter{Changes from Version (3.2.10)\label{app-changes-from-3.2.10}}
458462
(\citetitle[http://issues.apache.org/jira/browse/MODPYTHON-198]{MODPYTHON-198})
459463
Python 2.5 broke nested __auth__/__access__/__auth_realm__ in
460464
mod_python.publisher.
465+
\item
466+
(\citetitle[http://issues.apache.org/jira/browse/MODPYTHON-200]{MODPYTHON-200})
467+
Fixed problem whereby signed and marshalled cookies could not be used
468+
at the same time. When expecting marshalled cookie, any signed, but
469+
not marshalled cookies will be returned as normal cookies.
461470
\end{itemize}
462471

463472

Doc/modpython4.tex

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,7 +2181,7 @@ \subsection{Classes\label{pyapi-cookie-classes}}
21812181

21822182
\begin{notice}
21832183
Always check the types of objects returned by
2184-
\method{SignedCookie.parse()}.If it is an instance of
2184+
\method{SignedCookie.parse()}. If it is an instance of
21852185
\class{Cookie} (as opposed to \class{SignedCookie}), the
21862186
signature verification has failed:
21872187
\begin{verbatim}
@@ -2242,7 +2242,20 @@ \subsection{Functions\label{pyapi-cookie-func}}
22422242
can be any number of keyword arguments which, will be passed to
22432243
\method{parse()} (This is useful for \class{signedCookie} and
22442244
\class{MarshalCookie} which require \code{secret} as an additional
2245-
argument to \method{parse}).
2245+
argument to \method{parse}). The set of cookies found is returned as
2246+
a dictionary.
2247+
\end{funcdesc}
2248+
2249+
\begin{funcdesc}{get_cookie}{req, name \optional{, Class, data}}
2250+
This is a convenience function for retrieving a single named cookie
2251+
from incoming headers. \var{req} is a mod_python \class{Request}
2252+
object. \var{name} is the name of the cookie. \var{Class} is a class
2253+
whose \method{parse()} method will be used to parse the cookies, it
2254+
defaults to \code{Cookie}. \var{Data} can be any number of keyword
2255+
arguments which, will be passed to \method{parse()} (This is useful for
2256+
\class{signedCookie} and \class{MarshalCookie} which require
2257+
\code{secret} as an additional argument to \method{parse}). The cookie
2258+
if found is returned, otherwise \code{None} is returned.
22462259
\end{funcdesc}
22472260

22482261
\subsection{Examples\label{pyapi-cookie-example}}

README

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This is the Mod_Python README file. It consists of the following parts:
44

55
1. Getting Started
66
2. Flex
7-
3. New in 3.2
7+
3. New in 3.3
88
4. New in 3.0
99
5. Migrating from Mod_Python 2.7.8
1010
6. OS Hints
@@ -29,7 +29,11 @@ If you can't read instructions:
2929

3030
2. ./configure will generate a WARNING if it cannot find flex, or it
3131
is the wrong version. Generally you can ignore this warning and still
32-
successfully compile mod_python.
32+
successfully compile mod_python as the timestamp of the src/psp_parser.c
33+
file will be newer than that for the src/psp_parser.l file. If for some
34+
reason it still tries to run flex to regenerate the src/psp_parser.c
35+
file, running 'touch src/psp_parser.c' to update the timestamp should
36+
stop this and it will use the provided file.
3337

3438
The parser used by psp is written in C generated using flex. This
3539
requires a reentrant version of flex, which at this time is 2.5.31.
@@ -41,7 +45,7 @@ src/psp_parser.c file, you must get the correct flex version.
4145
You can specify the path the flex binary by using
4246
./configure --with-flex=/path/to/flex/binary.
4347

44-
3. New in 3.2
48+
3. New in 3.3
4549
Please refer to doc-html/ for more information.
4650

4751
4. New in 3.0

lib/python/mod_python/Cookie.py

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# vim: set sw=4 expandtab :
12
#
23
# Copyright 2004 Apache Software Foundation
34
#
@@ -107,14 +108,18 @@ class Cookie(object):
107108

108109
__metaclass__ = metaCookie
109110

110-
def parse(Class, str):
111+
DOWNGRADE = 0
112+
IGNORE = 1
113+
EXCEPTION = 3
114+
115+
def parse(Class, str, **kw):
111116
"""
112117
Parse a Cookie or Set-Cookie header value, and return
113118
a dict of Cookies. Note: the string should NOT include the
114119
header name, only the value.
115120
"""
116121

117-
dict = _parse_cookie(str, Class)
122+
dict = _parse_cookie(str, Class, **kw)
118123
return dict
119124

120125
parse = classmethod(parse)
@@ -173,18 +178,27 @@ class SignedCookie(Cookie):
173178
is still plainly visible as part of the cookie.
174179
"""
175180

176-
def parse(Class, s, secret):
181+
def parse(Class, s, secret, mismatch=Cookie.DOWNGRADE, **kw):
177182

178-
dict = _parse_cookie(s, Class)
183+
dict = _parse_cookie(s, Class, **kw)
179184

185+
del_list = []
180186
for k in dict:
181187
c = dict[k]
182188
try:
183189
c.unsign(secret)
184190
except CookieError:
185-
# downgrade to Cookie
186-
dict[k] = Cookie.parse(Cookie.__str__(c))[k]
187-
191+
if mismatch == Cookie.EXCEPTION:
192+
raise
193+
elif mismatch == Cookie.IGNORE:
194+
del_list.append(k)
195+
else:
196+
# downgrade to Cookie
197+
dict[k] = Cookie.parse(Cookie.__str__(c))[k]
198+
199+
for k in del_list:
200+
del dict[k]
201+
188202
return dict
189203

190204
parse = classmethod(parse)
@@ -244,17 +258,26 @@ class MarshalCookie(SignedCookie):
244258
http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&selm=7xn0hcugmy.fsf%40ruckus.brouhaha.com
245259
"""
246260

247-
def parse(Class, s, secret):
261+
def parse(Class, s, secret, mismatch=Cookie.DOWNGRADE, **kw):
248262

249-
dict = _parse_cookie(s, Class)
263+
dict = _parse_cookie(s, Class, **kw)
250264

265+
del_list = []
251266
for k in dict:
252267
c = dict[k]
253268
try:
254269
c.unmarshal(secret)
255-
except (CookieError, ValueError):
256-
# downgrade to Cookie
257-
dict[k] = Cookie.parse(Cookie.__str__(c))[k]
270+
except CookieError:
271+
if mismatch == Cookie.EXCEPTION:
272+
raise
273+
elif mismatch == Cookie.IGNORE:
274+
del_list.append(k)
275+
else:
276+
# downgrade to Cookie
277+
dict[k] = Cookie.parse(Cookie.__str__(c))[k]
278+
279+
for k in del_list:
280+
del dict[k]
258281

259282
return dict
260283

@@ -279,8 +302,16 @@ def __str__(self):
279302
def unmarshal(self, secret):
280303

281304
self.unsign(secret)
282-
self.value = marshal.loads(base64.decodestring(self.value))
283305

306+
try:
307+
data = base64.decodestring(self.value)
308+
except:
309+
raise CookieError, "Cannot base64 Decode Cookie: %s=%s" % (self.name, self.value)
310+
311+
try:
312+
self.value = marshal.loads(data)
313+
except (EOFError, ValueError, TypeError):
314+
raise CookieError, "Cannot Unmarshal Cookie: %s=%s" % (self.name, self.value)
284315

285316

286317
# This is a simplified and in some places corrected
@@ -301,7 +332,7 @@ def unmarshal(self, secret):
301332
r"\s*;?" # probably ending in a semi-colon
302333
)
303334

304-
def _parse_cookie(str, Class):
335+
def _parse_cookie(str, Class, names=None):
305336
# XXX problem is we should allow duplicate
306337
# strings
307338
result = {}
@@ -313,7 +344,7 @@ def _parse_cookie(str, Class):
313344

314345
# We just ditch the cookies names which start with a dollar sign since
315346
# those are in fact RFC2965 cookies attributes. See bug [#MODPYTHON-3].
316-
if key[0]!='$':
347+
if key[0]!='$' and names is None or key in names:
317348
result[key] = Class(key, val)
318349

319350
return result
@@ -351,3 +382,7 @@ def get_cookies(req, Class=Cookie, **kw):
351382

352383
return Class.parse(cookies, **kw)
353384

385+
def get_cookie(req, name, Class=Cookie, **kw):
386+
cookies = get_cookies(req, Class, names=[name], **kw)
387+
if cookies.has_key(name):
388+
return cookies[name]

lib/python/mod_python/Session.py

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# vim: set sw=4 expandtab :
12
#
23
# Copyright 2004 Apache Software Foundation
34
#
@@ -141,27 +142,35 @@ def __init__(self, req, sid=None, secret=None, lock=1,
141142
if config.has_key("mod_python.session.cookie_name"):
142143
session_cookie_name = config.get("mod_python.session.cookie_name", COOKIE_NAME)
143144
else:
144-
# For backwards compatability only.
145+
# For backwards compatability with versions
146+
# of mod_python prior to 3.3.
145147
session_cookie_name = config.get("session_cookie_name", COOKIE_NAME)
146148

147149
if not self._sid:
148150
# check to see if cookie exists
149151
if secret:
150-
cookies = Cookie.get_cookies(req, Class=Cookie.SignedCookie,
151-
secret=self._secret)
152+
cookie = Cookie.get_cookie(req, session_cookie_name,
153+
Class=Cookie.SignedCookie,
154+
secret=self._secret,
155+
mismatch=Cookie.Cookie.IGNORE)
152156
else:
153-
cookies = Cookie.get_cookies(req)
157+
cookie = Cookie.get_cookie(req, session_cookie_name)
158+
159+
if cookie:
160+
self._sid = cookie.value
154161

155-
if cookies.has_key(session_cookie_name):
156-
self._sid = cookies[session_cookie_name].value
157-
158162
if self._sid:
159-
# Validate the sid *before* locking the session
160-
# _check_sid will raise an apache.SERVER_RETURN exception
161163
if not _check_sid(self._sid):
162-
self._req.log_error("mod_python.Session warning: The session id contains invalid characters, valid characters are only 0-9 and a-f",
163-
apache.APLOG_WARNING)
164-
raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR
164+
if sid:
165+
# Supplied explicitly by user of the class,
166+
# raise an exception and make the user code
167+
# deal with it.
168+
raise ValueError("Invalid Session ID: sid=%s" % sid)
169+
else:
170+
# Derived from the cookie sent by browser,
171+
# wipe it out so it gets replaced with a
172+
# correct value.
173+
self._sid = None
165174

166175
self.init_lock()
167176

@@ -194,7 +203,8 @@ def make_cookie(self):
194203
if config.has_key("mod_python.session.cookie_name"):
195204
session_cookie_name = config.get("mod_python.session.cookie_name", COOKIE_NAME)
196205
else:
197-
# For backwards compatability only.
206+
# For backwards compatability with versions
207+
# of mod_python prior to 3.3.
198208
session_cookie_name = config.get("session_cookie_name", COOKIE_NAME)
199209

200210
if self._secret:
@@ -208,7 +218,8 @@ def make_cookie(self):
208218
if config.has_key("mod_python.session.application_path"):
209219
c.path = config["mod_python.session.application_path"]
210220
elif config.has_key("ApplicationPath"):
211-
# For backwards compatability only.
221+
# For backwards compatability with versions
222+
# of mod_python prior to 3.3.
212223
c.path = config["ApplicationPath"]
213224
else:
214225
# the path where *Handler directive was specified
@@ -342,14 +353,16 @@ def __init__(self, req, dbm=None, sid=0, secret=None, dbmtype=anydbm,
342353
if opts.has_key("mod_python.dbm_session.database_filename"):
343354
dbm = opts["mod_python.dbm_session.database_filename"]
344355
elif opts.has_key("session_dbm"):
345-
# For backwards compatability only.
356+
# For backwards compatability with versions
357+
# of mod_python prior to 3.3.
346358
dbm = opts["session_dbm"]
347359
elif opts.has_key("mod_python.dbm_session.database_directory"):
348360
dbm = os.path.join(opts.get('mod_python.dbm_session.database_directory', tempdir), 'mp_sess.dbm')
349361
elif opts.has_key("mod_python.session.database_directory"):
350362
dbm = os.path.join(opts.get('mod_python.session.database_directory', tempdir), 'mp_sess.dbm')
351363
else:
352-
# For backwards compatability only.
364+
# For backwards compatability with versions
365+
# of mod_python prior to 3.3.
353366
dbm = os.path.join(opts.get('session_directory', tempdir), 'mp_sess.dbm')
354367

355368
self._dbmfile = dbm
@@ -427,7 +440,8 @@ def __init__(self, req, sid=0, secret=None, timeout=0, lock=1,
427440
if opts.has_key('mod_python.file_session.enable_fast_cleanup'):
428441
self._fast_cleanup = true_or_false(opts.get('mod_python.file_session.enable_fast_cleanup', DFT_FAST_CLEANUP))
429442
else:
430-
# For backwards compatability only.
443+
# For backwards compatability with versions
444+
# of mod_python prior to 3.3.
431445
self._fast_cleanup = true_or_false(opts.get('session_fast_cleanup', DFT_FAST_CLEANUP))
432446
else:
433447
self._fast_cleanup = fast_cleanup
@@ -436,29 +450,33 @@ def __init__(self, req, sid=0, secret=None, timeout=0, lock=1,
436450
if opts.has_key('mod_python.file_session.verify_session_timeout'):
437451
self._verify_cleanup = true_or_false(opts.get('mod_python.file_session.verify_session_timeout', DFT_VERIFY_CLEANUP))
438452
else:
439-
# For backwards compatability only.
453+
# For backwards compatability with versions
454+
# of mod_python prior to 3.3.
440455
self._verify_cleanup = true_or_false(opts.get('session_verify_cleanup', DFT_VERIFY_CLEANUP))
441456
else:
442457
self._verify_cleanup = verify_cleanup
443458

444459
if opts.has_key('mod_python.file_session.cleanup_grace_period'):
445460
self._grace_period = int(opts.get('mod_python.file_session.cleanup_grace_period', DFT_GRACE_PERIOD))
446461
else:
447-
# For backwards compatability only.
462+
# For backwards compatability with versions
463+
# of mod_python prior to 3.3.
448464
self._grace_period = int(opts.get('session_grace_period', DFT_GRACE_PERIOD))
449465

450466
if opts.has_key('mod_python.file_session.cleanup_time_limit'):
451467
self._cleanup_time_limit = int(opts.get('mod_python.file_session.cleanup_time_limit',DFT_CLEANUP_TIME_LIMIT))
452468
else:
453-
# For backwards compatability only.
469+
# For backwards compatability with versions
470+
# of mod_python prior to 3.3.
454471
self._cleanup_time_limit = int(opts.get('session_cleanup_time_limit',DFT_CLEANUP_TIME_LIMIT))
455472

456473
if opts.has_key('mod_python.file_session.database_directory'):
457474
self._sessdir = os.path.join(opts.get('mod_python.file_session.database_directory', tempdir), 'mp_sess')
458475
elif opts.has_key('mod_python.session.database_directory'):
459476
self._sessdir = os.path.join(opts.get('mod_python.session.database_directory', tempdir), 'mp_sess')
460477
else:
461-
# For backwards compatability only.
478+
# For backwards compatability with versions
479+
# of mod_python prior to 3.3.
462480
self._sessdir = os.path.join(opts.get('session_directory', tempdir), 'mp_sess')
463481

464482
# FIXME
@@ -756,7 +774,8 @@ def Session(req, sid=0, secret=None, timeout=0, lock=1):
756774
if opts.has_key('mod_python.session.session_type'):
757775
sess_type = opts['mod_python.session.session_type']
758776
elif opts.has_key('session'):
759-
# For backwards compatability only.
777+
# For backwards compatability with versions
778+
# of mod_python prior to 3.3.
760779
sess_type = opts['session']
761780
else:
762781
# no session class in config so get the default for the platform

lib/python/mod_python/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@
2020
__all__ = ["apache", "cgihandler", "psp",
2121
"publisher", "util", "python22"]
2222

23-
version = "3.3.0-dev-20061105"
23+
version = "3.3.0-dev-20061107"
2424

lib/python/mod_python/psp.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ def cache_get(self, filename, mtime):
134134
if opts.has_key("mod_python.psp.cache_database_filename"):
135135
self.dbmcache = opts["mod_python.psp.cache_database_filename"]
136136
elif opts.has_key("PSPDbmCache"):
137-
# For backwards compatability only.
137+
# For backwards compatability with versions
138+
# of mod_python prior to 3.3.
138139
self.dbmcache = opts["PSPDbmCache"]
139140

140141
if self.dbmcache:

src/include/mpversion.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#define MPV_MAJOR 3
22
#define MPV_MINOR 3
33
#define MPV_PATCH 0
4-
#define MPV_BUILD 20061105
5-
#define MPV_STRING "3.3.0-dev-20061105"
4+
#define MPV_BUILD 20061107
5+
#define MPV_STRING "3.3.0-dev-20061107"

0 commit comments

Comments
 (0)