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