~ubuntu-branches/ubuntu/karmic/libapache2-mod-python/karmic-updates

« back to all changes in this revision

Viewing changes to lib/python/mod_python/util.py

  • Committer: Bazaar Package Importer
  • Author(s): Thom May
  • Date: 2004-09-06 20:27:57 UTC
  • Revision ID: james.westby@ubuntu.com-20040906202757-yzpyu1bcabgpjtiu
Tags: upstream-3.1.3
ImportĀ upstreamĀ versionĀ 3.1.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 #
 
2
 # Copyright 2004 Apache Software Foundation 
 
3
 # 
 
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
 
7
 #
 
8
 #      http://www.apache.org/licenses/LICENSE-2.0
 
9
 #
 
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.
 
15
 #
 
16
 # Originally developed by Gregory Trubetskoy.
 
17
 #
 
18
 # $Id: util.py,v 1.21 2004/02/16 19:47:27 grisha Exp $
 
19
 
 
20
import _apache
 
21
import apache
 
22
import cStringIO
 
23
import tempfile
 
24
 
 
25
from types import *
 
26
from exceptions import *
 
27
 
 
28
parse_qs = _apache.parse_qs
 
29
parse_qsl = _apache.parse_qsl
 
30
 
 
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
 
33
    same functionality.
 
34
 
 
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.
 
38
 
 
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.
 
42
"""
 
43
 
 
44
class Field:
 
45
 
 
46
    filename = None
 
47
    headers = {}
 
48
 
 
49
    def __init__(self, name, file, ctype, type_options,
 
50
                 disp, disp_options):
 
51
        self.name = name
 
52
        self.file = file
 
53
        self.type = ctype
 
54
        self.type_options = type_options
 
55
        self.disposition = disp
 
56
        self.disposition_options = disp_options
 
57
 
 
58
    def __repr__(self):
 
59
        """Return printable representation."""
 
60
        return "Field(%s, %s)" % (`self.name`, `self.value`)
 
61
 
 
62
    def __getattr__(self, name):
 
63
        if name != 'value':
 
64
            raise AttributeError, name
 
65
        if self.file:
 
66
            self.file.seek(0)
 
67
            value = self.file.read()
 
68
            self.file.seek(0)
 
69
        else:
 
70
            value = None
 
71
        return value
 
72
 
 
73
    def __del__(self):
 
74
        self.file.close()
 
75
 
 
76
class StringField(str):
 
77
    """ This class is basically a string with
 
78
    a value attribute for compatibility with std lib cgi.py
 
79
    """
 
80
    
 
81
    def __init__(self, str=""):
 
82
        str.__init__(self, str)
 
83
        self.value = self.__str__()
 
84
 
 
85
class FieldStorage:
 
86
 
 
87
    def __init__(self, req, keep_blank_values=0, strict_parsing=0):
 
88
 
 
89
        self.list = []
 
90
 
 
91
        # always process GET-style parameters
 
92
        if req.args:
 
93
            pairs = parse_qsl(req.args, keep_blank_values)
 
94
            for pair in pairs:
 
95
                file = cStringIO.StringIO(pair[1])
 
96
                self.list.append(Field(pair[0], file, "text/plain", {},
 
97
                                       None, {}))
 
98
 
 
99
        if req.method == "POST":
 
100
 
 
101
            try:
 
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
 
106
 
 
107
            if not req.headers_in.has_key("content-type"):
 
108
                ctype = "application/x-www-form-urlencoded"
 
109
            else:
 
110
                ctype = req.headers_in["content-type"]
 
111
 
 
112
            if ctype == "application/x-www-form-urlencoded":
 
113
                
 
114
                pairs = parse_qsl(req.read(clen), keep_blank_values)
 
115
                for pair in pairs:
 
116
                    file = cStringIO.StringIO(pair[1])
 
117
                    self.list.append(Field(pair[0], file, "text/plain",
 
118
                                     {}, None, {}))
 
119
 
 
120
            elif ctype[:10] == "multipart/":
 
121
 
 
122
                # figure out boundary
 
123
                try:
 
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
 
129
                except ValueError:
 
130
                    raise apache.SERVER_RETURN, apache.HTTP_BAD_REQUEST
 
131
 
 
132
                #read until boundary
 
133
                line = req.readline()
 
134
                sline = line.strip()
 
135
                while line and sline != boundary:
 
136
                    line = req.readline()
 
137
                    sline = line.strip()
 
138
 
 
139
                while 1:
 
140
 
 
141
                    ## parse headers
 
142
                    
 
143
                    ctype, type_options = "text/plain", {}
 
144
                    disp, disp_options = None, {}
 
145
                    headers = apache.make_table()
 
146
 
 
147
                    line = req.readline()
 
148
                    sline = line.strip()
 
149
                    if not line or sline == (boundary + "--"):
 
150
                        break
 
151
                    
 
152
                    while line and line not in ["\n", "\r\n"]:
 
153
                        h, v = line.split(":", 1)
 
154
                        headers.add(h, v)
 
155
                        h = h.lower()
 
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()
 
161
                        sline = line.strip()
 
162
 
 
163
                    if disp_options.has_key("name"):
 
164
                        name = disp_options["name"]
 
165
                    else:
 
166
                        name = None
 
167
 
 
168
                    # is this a file?
 
169
                    if disp_options.has_key("filename"):
 
170
                        file = self.make_file()
 
171
                    else:
 
172
                        file = cStringIO.StringIO()
 
173
 
 
174
                    # read it in
 
175
                    self.read_to_boundary(req, boundary, file)
 
176
                    file.seek(0)
 
177
 
 
178
                    # make a Field
 
179
                    field = Field(name, file, ctype, type_options,
 
180
                                  disp, disp_options)
 
181
                    field.headers = headers
 
182
                    if disp_options.has_key("filename"):
 
183
                        field.filename = disp_options["filename"]
 
184
 
 
185
                    self.list.append(field)
 
186
 
 
187
            else:
 
188
                # we don't understand this content-type
 
189
                raise apache.SERVER_RETURN, apache.HTTP_NOT_IMPLEMENTED
 
190
 
 
191
 
 
192
    def make_file(self):
 
193
        return tempfile.TemporaryFile("w+b")
 
194
 
 
195
    def skip_to_boundary(self, req, boundary):
 
196
        line = req.readline()
 
197
        sline = line.strip()
 
198
        last_bound = boundary + "--"
 
199
        while line and sline != boundary and sline != last_bound:
 
200
            line = req.readline()
 
201
            sline = line.strip()
 
202
 
 
203
    def read_to_boundary(self, req, boundary, file):
 
204
        delim = ""
 
205
        line = req.readline()
 
206
        sline = line.strip()
 
207
        last_bound = boundary + "--"
 
208
        while line and sline != boundary and sline != last_bound:
 
209
            odelim = delim
 
210
            if line[-2:] == "\r\n":
 
211
                delim = "\r\n"
 
212
                line = line[:-2]
 
213
            elif line[-1:] == "\n":
 
214
                delim = "\n"
 
215
                line = line[:-1]
 
216
            file.write(odelim + line)
 
217
            line = req.readline()
 
218
            sline = line.strip()
 
219
 
 
220
    def __getitem__(self, key):
 
221
        """Dictionary style indexing."""
 
222
        if self.list is None:
 
223
            raise TypeError, "not indexable"
 
224
        found = []
 
225
        for item in self.list:
 
226
            if item.name == key:
 
227
                if isinstance(item.file, FileType):
 
228
                    found.append(item)
 
229
                else:
 
230
                    found.append(StringField(item.value))
 
231
        if not found:
 
232
            raise KeyError, key
 
233
        if len(found) == 1:
 
234
            return found[0]
 
235
        else:
 
236
            return found
 
237
 
 
238
    def get(self, key, default):
 
239
        try:
 
240
            return self.__getitem__(key)
 
241
        except KeyError:
 
242
            return default
 
243
 
 
244
    def keys(self):
 
245
        """Dictionary style keys() method."""
 
246
        if self.list is None:
 
247
            raise TypeError, "not indexable"
 
248
        keys = []
 
249
        for item in self.list:
 
250
            if item.name not in keys: keys.append(item.name)
 
251
        return keys
 
252
 
 
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
 
259
        return 0
 
260
 
 
261
    __contains__ = has_key
 
262
 
 
263
    def __len__(self):
 
264
        """Dictionary style len(x) support."""
 
265
        return len(self.keys())
 
266
 
 
267
    def getfirst(self, key, default=None):
 
268
        """ return the first value received """
 
269
        for item in self.list:
 
270
            if item.name == key:
 
271
                if isinstance(item.file, FileType):
 
272
                    return item
 
273
                else:
 
274
                    return StringField(item.value)
 
275
        return default
 
276
                                                                    
 
277
    def getlist(self, key):
 
278
        """ return a list of received values """
 
279
        if self.list is None:
 
280
            raise TypeError, "not indexable"
 
281
        found = []
 
282
        for item in self.list:
 
283
            if item.name == key:
 
284
                if isinstance(item.file, FileType):
 
285
                    found.append(item)
 
286
                else:
 
287
                    found.append(StringField(item.value))
 
288
        return found
 
289
 
 
290
def parse_header(line):
 
291
    """Parse a Content-type like header.
 
292
 
 
293
    Return the main content-type and a dictionary of options.
 
294
 
 
295
    """
 
296
    
 
297
    plist = map(lambda a: a.strip(), line.split(';'))
 
298
    key = plist[0].lower()
 
299
    del plist[0]
 
300
    pdict = {}
 
301
    for p in plist:
 
302
        i = p.find('=')
 
303
        if i >= 0:
 
304
            name = p[:i].strip().lower()
 
305
            value = p[i+1:].strip()
 
306
            if len(value) >= 2 and value[0] == value[-1] == '"':
 
307
                value = value[1:-1]
 
308
            pdict[name] = value
 
309
    return key, pdict
 
310
 
 
311
def apply_fs_data(object, fs, **args):
 
312
    """
 
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.
 
316
    """
 
317
 
 
318
    # add form data to args
 
319
    for field in fs.list:
 
320
        if field.filename:
 
321
            val = field
 
322
        else:
 
323
            val = field.value
 
324
        args.setdefault(field.name, []).append(val)
 
325
 
 
326
    # replace lists with single values
 
327
    for arg in args:
 
328
        if ((type(args[arg]) is ListType) and
 
329
            (len(args[arg]) == 1)):
 
330
            args[arg] = args[arg][0]
 
331
 
 
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:
 
335
 
 
336
    if type(object) is InstanceType:
 
337
        # instances are callable when they have __call__()
 
338
        object = object.__call__
 
339
 
 
340
    expected = []
 
341
    if hasattr(object, "func_code"):
 
342
        # function
 
343
        fc = object.func_code
 
344
        expected = fc.co_varnames[0:fc.co_argcount]
 
345
    elif hasattr(object, 'im_func'):
 
346
        # method
 
347
        fc = object.im_func.func_code
 
348
        expected = fc.co_varnames[1:fc.co_argcount]
 
349
    elif type(object) is ClassType:
 
350
        # class
 
351
        fc = object.__init__.im_func.func_code
 
352
        expected = fc.co_varnames[1:fc.co_argcount]
 
353
 
 
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:
 
359
                del args[name]
 
360
 
 
361
    return object(**args)
 
362
 
 
363
def redirect(req, location, permanent=0, text=None):
 
364
    """
 
365
    A convenience function to provide redirection
 
366
    """
 
367
 
 
368
    if req.sent_bodyct:
 
369
        raise IOError, "Cannot redirect after headers have already been sent."
 
370
 
 
371
    req.err_headers_out["Location"] = location
 
372
    if permanent:
 
373
        req.status = apache.HTTP_MOVED_PERMANENTLY
 
374
    else:
 
375
        req.status = apache.HTTP_MOVED_TEMPORARILY
 
376
 
 
377
    if text is None:
 
378
        req.write('<p>The document has moved' 
 
379
                  ' <a href="%s">here</a></p>\n'
 
380
                  % location)
 
381
    else:
 
382
        req.write(text)
 
383
 
 
384
    raise apache.SERVER_RETURN, apache.OK
 
385