Merge branch 'master' into python3
[wrw.git] / wrw / form.py
CommitLineData
b409a338 1import cgi
8b7cf278 2from . import proto
b409a338
FT
3
4__all__ = ["formdata"]
5
6class formwrap(object):
7 def __init__(self, req):
aba06d86 8 if req.ihead.get("Content-Type") == "application/x-www-form-urlencoded":
0417f41c 9 self.cf = cgi.parse(environ = req.env, fp = req.input)
b409a338
FT
10 else:
11 self.cf = cgi.parse(environ = req.env)
12
13 def __getitem__(self, key):
14 return self.cf[key][0]
15
16 def get(self, key, default = ""):
17 if key in self:
18 return self.cf[key][0]
19 return default
20
21 def __contains__(self, key):
22 return key in self.cf and len(self.cf[key]) > 0
23
24 def __iter__(self):
25 return iter(self.cf)
26
27 def items(self):
28 def iter():
29 for key, list in self.cf.items():
30 for val in list:
31 yield key, val
32 return list(iter())
33
34 def keys(self):
c33f2d6c 35 return list(self.cf.keys())
b409a338
FT
36
37 def values(self):
38 return [val for key, val in self.items()]
39
c21c8713
FT
40class badmultipart(Exception):
41 pass
42
43class formpart(object):
44 def __init__(self, form):
45 self.form = form
289fc162 46 self.buf = b""
c21c8713
FT
47 self.eof = False
48 self.head = {}
49
50 def parsehead(self):
51 pass
52
53 def fillbuf(self, sz):
54 req = self.form.req
289fc162
FT
55 mboundary = b"\r\n--" + self.form.boundary + b"\r\n"
56 lboundary = b"\r\n--" + self.form.boundary + b"--\r\n"
c21c8713
FT
57 while not self.eof:
58 p = self.form.buf.find(mboundary)
59 if p >= 0:
60 self.buf += self.form.buf[:p]
61 self.form.buf = self.form.buf[p + len(mboundary):]
62 self.eof = True
63 break
64 p = self.form.buf.find(lboundary)
65 if p >= 0:
66 self.buf += self.form.buf[:p]
67 self.form.buf = self.form.buf[p + len(lboundary):]
68 self.eof = True
69 self.form.eof = True
70 break
71 self.buf += self.form.buf[:-len(lboundary)]
72 self.form.buf = self.form.buf[-len(lboundary):]
73 if sz >= 0 and len(self.buf) >= sz:
74 break
75 while len(self.form.buf) <= len(lboundary):
0417f41c 76 ret = req.input.read(8192)
c21c8713
FT
77 if ret == "":
78 raise badmultipart("Missing last multipart boundary")
79 self.form.buf += ret
80
81 def read(self, limit = -1):
82 self.fillbuf(limit)
83 if limit >= 0:
84 ret = self.buf[:limit]
85 self.buf = self.buf[limit:]
86 else:
87 ret = self.buf
88 self.buf = ""
89 return ret
90
91 def readline(self, limit = -1):
92 last = 0
93 while True:
289fc162 94 p = self.buf.find(b'\n', last)
c21c8713
FT
95 if p < 0:
96 if self.eof:
97 ret = self.buf
98 self.buf = ""
99 return ret
100 last = len(self.buf)
101 self.fillbuf(last + 128)
102 else:
103 ret = self.buf[:p + 1]
104 self.buf = self.buf[p + 1:]
105 return ret
106
107 def close(self):
108 self.fillbuf(-1)
109
110 def __enter__(self):
111 return self
112
113 def __exit__(self, *excinfo):
4e033e2b 114 self.close()
c21c8713
FT
115 return False
116
289fc162 117 def parsehead(self, charset):
c21c8713
FT
118 def headline():
119 ln = self.readline(256)
289fc162 120 if ln[-1] != ord(b'\n'):
c21c8713 121 raise badmultipart("Too long header line in part")
289fc162
FT
122 try:
123 return ln.decode(charset).rstrip()
124 except UnicodeError:
125 raise badmultipart("Form part header is not in assumed charset")
c21c8713
FT
126
127 ln = headline()
128 while True:
129 if ln == "":
130 break
131 buf = ln
132 while True:
133 ln = headline()
134 if not ln[1:].isspace():
135 break
136 buf += ln.lstrip()
137 p = buf.find(':')
138 if p < 0:
139 raise badmultipart("Malformed multipart header line")
140 self.head[buf[:p].strip().lower()] = buf[p + 1:].lstrip()
141
142 val, par = proto.pmimehead(self.head.get("content-disposition", ""))
143 if val != "form-data":
144 raise badmultipart("Unexpected Content-Disposition in form part: %r" % val)
145 if not "name" in par:
146 raise badmultipart("Missing name in form part")
147 self.name = par["name"]
148 self.filename = par.get("filename")
149 val, par = proto.pmimehead(self.head.get("content-type", ""))
150 self.ctype = val
151 self.charset = par.get("charset")
152 encoding = self.head.get("content-transfer-encoding", "binary")
153 if encoding != "binary":
154 raise badmultipart("Form part uses unexpected transfer encoding: %r" % encoding)
155
156class multipart(object):
289fc162 157 def __init__(self, req, charset):
c21c8713
FT
158 val, par = proto.pmimehead(req.ihead.get("Content-Type", ""))
159 if req.method != "POST" or val != "multipart/form-data":
160 raise badmultipart("Request is not a multipart form")
161 if "boundary" not in par:
162 raise badmultipart("Multipart form lacks boundary")
289fc162
FT
163 try:
164 self.boundary = par["boundary"].encode("us-ascii")
165 except UnicodeError:
166 raise badmultipart("Multipart boundary must be ASCII string")
c21c8713 167 self.req = req
289fc162 168 self.buf = b"\r\n"
c21c8713 169 self.eof = False
289fc162 170 self.headcs = charset
c21c8713
FT
171 self.lastpart = formpart(self)
172 self.lastpart.close()
173
174 def __iter__(self):
175 return self
176
289fc162 177 def __next__(self):
c21c8713
FT
178 if not self.lastpart.eof:
179 raise RuntimeError("All form parts must be read entirely")
180 if self.eof:
181 raise StopIteration()
182 self.lastpart = formpart(self)
289fc162 183 self.lastpart.parsehead(self.headcs)
c21c8713
FT
184 return self.lastpart
185
b409a338
FT
186def formdata(req):
187 return req.item(formwrap)