|
1 """This package contains all WSGI specific code for cubicweb |
|
2 |
|
3 NOTE: this package borrows a lot of code to Django |
|
4 (http://www.djangoproject.com) and to the wsgiref module |
|
5 of the python2.5's stdlib. |
|
6 |
|
7 WSGI corresponding PEP: http://www.python.org/dev/peps/pep-0333/ |
|
8 |
|
9 :organization: Logilab |
|
10 :copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
11 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
12 """ |
|
13 __docformat__ = "restructuredtext en" |
|
14 |
|
15 from email import message, message_from_string |
|
16 from Cookie import SimpleCookie |
|
17 from StringIO import StringIO |
|
18 from cgi import parse_header, parse_qsl |
|
19 from pprint import pformat as _pformat |
|
20 |
|
21 |
|
22 def pformat(obj): |
|
23 """pretty prints `obj` if possible""" |
|
24 try: |
|
25 return _pformat(obj) |
|
26 except: |
|
27 return u'<could not parse>' |
|
28 |
|
29 def qs2dict(qs): |
|
30 """transforms a query string into a regular python dict""" |
|
31 result = {} |
|
32 for key, value in parse_qsl(qs, True): |
|
33 result.setdefault(key, []).append(value) |
|
34 return result |
|
35 |
|
36 def normalize_header(header): |
|
37 """returns a normalized header name |
|
38 |
|
39 >>> normalize_header('User_Agent') |
|
40 'User-agent' |
|
41 """ |
|
42 return header.replace('_', '-').capitalize() |
|
43 |
|
44 def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0): |
|
45 """ |
|
46 THIS COMES FROM DJANGO |
|
47 A version of shutil.copyfileobj that will not read more than 'size' bytes. |
|
48 This makes it safe from clients sending more than CONTENT_LENGTH bytes of |
|
49 data in the body. |
|
50 """ |
|
51 if not size: |
|
52 return |
|
53 while size > 0: |
|
54 buf = fsrc.read(min(length, size)) |
|
55 if not buf: |
|
56 break |
|
57 fdst.write(buf) |
|
58 size -= len(buf) |
|
59 |
|
60 def parse_file_upload(header_dict, post_data): |
|
61 """This is adapted FROM DJANGO""" |
|
62 raw_message = '\r\n'.join('%s:%s' % pair for pair in header_dict.iteritems()) |
|
63 raw_message += '\r\n\r\n' + post_data |
|
64 msg = message_from_string(raw_message) |
|
65 post, files = {}, {} |
|
66 for submessage in msg.get_payload(): |
|
67 name_dict = parse_header(submessage['Content-Disposition'])[1] |
|
68 key = name_dict['name'] |
|
69 # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads |
|
70 # or {'name': 'blah'} for POST fields |
|
71 # We assume all uploaded files have a 'filename' set. |
|
72 if 'filename' in name_dict: |
|
73 assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported" |
|
74 if not name_dict['filename'].strip(): |
|
75 continue |
|
76 # IE submits the full path, so trim everything but the basename. |
|
77 # (We can't use os.path.basename because that uses the server's |
|
78 # directory separator, which may not be the same as the |
|
79 # client's one.) |
|
80 filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:] |
|
81 mimetype = 'Content-Type' in submessage and submessage['Content-Type'] or None |
|
82 content = StringIO(submessage.get_payload()) |
|
83 files[key] = [filename, mimetype, content] |
|
84 else: |
|
85 post.setdefault(key, []).append(submessage.get_payload()) |
|
86 return post, files |
|
87 |