Merge branch 'master' into python2
[wrw.git] / wrw / form.py
1 import urlparse
2 import proto
3
4 __all__ = ["formdata"]
5
6 def formparse(req):
7     buf = {}
8     buf.update(urlparse.parse_qsl(req.query))
9     if req.ihead.get("Content-Type") == "application/x-www-form-urlencoded":
10         try:
11             rbody = req.input.read(2 ** 20)
12         except IOError as exc:
13             return exc
14         if len(rbody) >= 2 ** 20:
15             return ValueError("x-www-form-urlencoded data is absurdly long")
16         buf.update(urlparse.parse_qsl(rbody))
17     return buf
18
19 class badmultipart(Exception):
20     pass
21
22 class formpart(object):
23     def __init__(self, form):
24         self.form = form
25         self.buf = ""
26         self.eof = False
27         self.head = {}
28
29     def parsehead(self):
30         pass
31
32     def fillbuf(self, sz):
33         req = self.form.req
34         mboundary = "\r\n--" + self.form.boundary + "\r\n"
35         lboundary = "\r\n--" + self.form.boundary + "--\r\n"
36         while not self.eof:
37             p = self.form.buf.find(mboundary)
38             if p >= 0:
39                 self.buf += self.form.buf[:p]
40                 self.form.buf = self.form.buf[p + len(mboundary):]
41                 self.eof = True
42                 break
43             p = self.form.buf.find(lboundary)
44             if p >= 0:
45                 self.buf += self.form.buf[:p]
46                 self.form.buf = self.form.buf[p + len(lboundary):]
47                 self.eof = True
48                 self.form.eof = True
49                 break
50             self.buf += self.form.buf[:-len(lboundary)]
51             self.form.buf = self.form.buf[-len(lboundary):]
52             if sz >= 0 and len(self.buf) >= sz:
53                 break
54             while len(self.form.buf) <= len(lboundary):
55                 ret = req.input.read(8192)
56                 if ret == "":
57                     raise badmultipart("Missing last multipart boundary")
58                 self.form.buf += ret
59
60     def read(self, limit=-1):
61         self.fillbuf(limit)
62         if limit >= 0:
63             ret = self.buf[:limit]
64             self.buf = self.buf[limit:]
65         else:
66             ret = self.buf
67             self.buf = ""
68         return ret
69
70     def readline(self, limit=-1):
71         last = 0
72         while True:
73             p = self.buf.find('\n', last)
74             if p < 0:
75                 if self.eof:
76                     ret = self.buf
77                     self.buf = ""
78                     return ret
79                 last = len(self.buf)
80                 self.fillbuf(last + 128)
81             else:
82                 ret = self.buf[:p + 1]
83                 self.buf = self.buf[p + 1:]
84                 return ret
85
86     def close(self):
87         while True:
88             if self.read(8192) == "":
89                 break
90
91     def __enter__(self):
92         return self
93
94     def __exit__(self, *excinfo):
95         self.close()
96         return False
97
98     def parsehead(self):
99         def headline():
100             ln = self.readline(256)
101             if ln[-1] != '\n':
102                 raise badmultipart("Too long header line in part")
103             return ln.rstrip()
104
105         ln = headline()
106         while True:
107             if ln == "":
108                 break
109             buf = ln
110             while True:
111                 ln = headline()
112                 if not ln[1:].isspace():
113                     break
114                 buf += ln.lstrip()
115             p = buf.find(':')
116             if p < 0:
117                 raise badmultipart("Malformed multipart header line")
118             self.head[buf[:p].strip().lower()] = buf[p + 1:].lstrip()
119
120         val, par = proto.pmimehead(self.head.get("content-disposition", ""))
121         if val != "form-data":
122             raise badmultipart("Unexpected Content-Disposition in form part: %r" % val)
123         if not "name" in par:
124             raise badmultipart("Missing name in form part")
125         self.name = par["name"]
126         self.filename = par.get("filename")
127         val, par = proto.pmimehead(self.head.get("content-type", ""))
128         self.ctype = val
129         self.charset = par.get("charset")
130         encoding = self.head.get("content-transfer-encoding", "binary")
131         if encoding != "binary":
132             raise badmultipart("Form part uses unexpected transfer encoding: %r" % encoding)
133
134 class multipart(object):
135     def __init__(self, req):
136         val, par = proto.pmimehead(req.ihead.get("Content-Type", ""))
137         if req.method != "POST" or val != "multipart/form-data":
138             raise badmultipart("Request is not a multipart form")
139         if "boundary" not in par:
140             raise badmultipart("Multipart form lacks boundary")
141         self.boundary = par["boundary"]
142         self.req = req
143         self.buf = "\r\n"
144         self.eof = False
145         self.lastpart = formpart(self)
146         self.lastpart.close()
147
148     def __iter__(self):
149         return self
150
151     def next(self):
152         if not self.lastpart.eof:
153             raise RuntimeError("All form parts must be read entirely")
154         if self.eof:
155             raise StopIteration()
156         self.lastpart = formpart(self)
157         self.lastpart.parsehead()
158         return self.lastpart
159
160 def formdata(req, onerror=Exception):
161     data = req.item(formparse)
162     if isinstance(data, Exception):
163         if onerror is Exception:
164             raise data
165         return onerror
166     return data