2
# Copyright 2004 Apache Software Foundation
4
# Licensed under the Apache License, Version 2.0 (the "License"); you
5
# may not use this file except in compliance with the License. You
6
# may obtain a copy of the License at
8
# http://www.apache.org/licenses/LICENSE-2.0
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
# implied. See the License for the specific language governing
14
# permissions and limitations under the License.
16
# Originally developed by Gregory Trubetskoy.
18
# $Id: util.py,v 1.21 2004/02/16 19:47:27 grisha Exp $
26
from exceptions import *
28
parse_qs = _apache.parse_qs
29
parse_qsl = _apache.parse_qsl
31
""" The classes below are a (almost) a drop-in replacement for the
32
standard cgi.py FieldStorage class. They should have pretty much the
35
These classes differ in that unlike cgi.FieldStorage, they are not
36
recursive. The class FieldStorage contains a list of instances of
37
Field class. Field class is incapable of storing anything in it.
39
These objects should be considerably faster than the ones in cgi.py
40
because they do not expect CGI environment, and are
41
optimized specifically for Apache and mod_python.
49
def __init__(self, name, file, ctype, type_options,
54
self.type_options = type_options
55
self.disposition = disp
56
self.disposition_options = disp_options
59
"""Return printable representation."""
60
return "Field(%s, %s)" % (`self.name`, `self.value`)
62
def __getattr__(self, name):
64
raise AttributeError, name
67
value = self.file.read()
76
class StringField(str):
77
""" This class is basically a string with
78
a value attribute for compatibility with std lib cgi.py
81
def __init__(self, str=""):
82
str.__init__(self, str)
83
self.value = self.__str__()
87
def __init__(self, req, keep_blank_values=0, strict_parsing=0):
91
# always process GET-style parameters
93
pairs = parse_qsl(req.args, keep_blank_values)
95
file = cStringIO.StringIO(pair[1])
96
self.list.append(Field(pair[0], file, "text/plain", {},
99
if req.method == "POST":
102
clen = int(req.headers_in["content-length"])
103
except (KeyError, ValueError):
104
# absent content-length is not acceptable
105
raise apache.SERVER_RETURN, apache.HTTP_LENGTH_REQUIRED
107
if not req.headers_in.has_key("content-type"):
108
ctype = "application/x-www-form-urlencoded"
110
ctype = req.headers_in["content-type"]
112
if ctype == "application/x-www-form-urlencoded":
114
pairs = parse_qsl(req.read(clen), keep_blank_values)
116
file = cStringIO.StringIO(pair[1])
117
self.list.append(Field(pair[0], file, "text/plain",
120
elif ctype[:10] == "multipart/":
122
# figure out boundary
124
i = ctype.lower().rindex("boundary=")
125
boundary = ctype[i+9:]
126
if len(boundary) >= 2 and boundary[0] == boundary[-1] == '"':
127
boundary = boundary[1:-1]
128
boundary = "--" + boundary
130
raise apache.SERVER_RETURN, apache.HTTP_BAD_REQUEST
133
line = req.readline()
135
while line and sline != boundary:
136
line = req.readline()
143
ctype, type_options = "text/plain", {}
144
disp, disp_options = None, {}
145
headers = apache.make_table()
147
line = req.readline()
149
if not line or sline == (boundary + "--"):
152
while line and line not in ["\n", "\r\n"]:
153
h, v = line.split(":", 1)
156
if h == "content-disposition":
157
disp, disp_options = parse_header(v)
158
elif h == "content-type":
159
ctype, type_options = parse_header(v)
160
line = req.readline()
163
if disp_options.has_key("name"):
164
name = disp_options["name"]
169
if disp_options.has_key("filename"):
170
file = self.make_file()
172
file = cStringIO.StringIO()
175
self.read_to_boundary(req, boundary, file)
179
field = Field(name, file, ctype, type_options,
181
field.headers = headers
182
if disp_options.has_key("filename"):
183
field.filename = disp_options["filename"]
185
self.list.append(field)
188
# we don't understand this content-type
189
raise apache.SERVER_RETURN, apache.HTTP_NOT_IMPLEMENTED
193
return tempfile.TemporaryFile("w+b")
195
def skip_to_boundary(self, req, boundary):
196
line = req.readline()
198
last_bound = boundary + "--"
199
while line and sline != boundary and sline != last_bound:
200
line = req.readline()
203
def read_to_boundary(self, req, boundary, file):
205
line = req.readline()
207
last_bound = boundary + "--"
208
while line and sline != boundary and sline != last_bound:
210
if line[-2:] == "\r\n":
213
elif line[-1:] == "\n":
216
file.write(odelim + line)
217
line = req.readline()
220
def __getitem__(self, key):
221
"""Dictionary style indexing."""
222
if self.list is None:
223
raise TypeError, "not indexable"
225
for item in self.list:
227
if isinstance(item.file, FileType):
230
found.append(StringField(item.value))
238
def get(self, key, default):
240
return self.__getitem__(key)
245
"""Dictionary style keys() method."""
246
if self.list is None:
247
raise TypeError, "not indexable"
249
for item in self.list:
250
if item.name not in keys: keys.append(item.name)
253
def has_key(self, key):
254
"""Dictionary style has_key() method."""
255
if self.list is None:
256
raise TypeError, "not indexable"
257
for item in self.list:
258
if item.name == key: return 1
261
__contains__ = has_key
264
"""Dictionary style len(x) support."""
265
return len(self.keys())
267
def getfirst(self, key, default=None):
268
""" return the first value received """
269
for item in self.list:
271
if isinstance(item.file, FileType):
274
return StringField(item.value)
277
def getlist(self, key):
278
""" return a list of received values """
279
if self.list is None:
280
raise TypeError, "not indexable"
282
for item in self.list:
284
if isinstance(item.file, FileType):
287
found.append(StringField(item.value))
290
def parse_header(line):
291
"""Parse a Content-type like header.
293
Return the main content-type and a dictionary of options.
297
plist = map(lambda a: a.strip(), line.split(';'))
298
key = plist[0].lower()
304
name = p[:i].strip().lower()
305
value = p[i+1:].strip()
306
if len(value) >= 2 and value[0] == value[-1] == '"':
311
def apply_fs_data(object, fs, **args):
313
Apply FieldStorage data to an object - the object must be
314
callable. Examine the args, and match then with fs data,
315
then call the object, return the result.
318
# add form data to args
319
for field in fs.list:
324
args.setdefault(field.name, []).append(val)
326
# replace lists with single values
328
if ((type(args[arg]) is ListType) and
329
(len(args[arg]) == 1)):
330
args[arg] = args[arg][0]
332
# we need to weed out unexpected keyword arguments
333
# and for that we need to get a list of them. There
334
# are a few options for callable objects here:
336
if type(object) is InstanceType:
337
# instances are callable when they have __call__()
338
object = object.__call__
341
if hasattr(object, "func_code"):
343
fc = object.func_code
344
expected = fc.co_varnames[0:fc.co_argcount]
345
elif hasattr(object, 'im_func'):
347
fc = object.im_func.func_code
348
expected = fc.co_varnames[1:fc.co_argcount]
349
elif type(object) is ClassType:
351
fc = object.__init__.im_func.func_code
352
expected = fc.co_varnames[1:fc.co_argcount]
354
# remove unexpected args unless co_flags & 0x08,
355
# meaning function accepts **kw syntax
356
if not (fc.co_flags & 0x08):
357
for name in args.keys():
358
if name not in expected:
361
return object(**args)
363
def redirect(req, location, permanent=0, text=None):
365
A convenience function to provide redirection
369
raise IOError, "Cannot redirect after headers have already been sent."
371
req.err_headers_out["Location"] = location
373
req.status = apache.HTTP_MOVED_PERMANENTLY
375
req.status = apache.HTTP_MOVED_TEMPORARILY
378
req.write('<p>The document has moved'
379
' <a href="%s">here</a></p>\n'
384
raise apache.SERVER_RETURN, apache.OK