1 | // |
2 | // HTMLForm.cpp |
3 | // |
4 | // Library: Net |
5 | // Package: HTML |
6 | // Module: HTMLForm |
7 | // |
8 | // Copyright (c) 2005-2006, Applied Informatics Software Engineering GmbH. |
9 | // and Contributors. |
10 | // |
11 | // SPDX-License-Identifier: BSL-1.0 |
12 | // |
13 | |
14 | |
15 | #include "Poco/Net/HTMLForm.h" |
16 | #include "Poco/Net/HTTPRequest.h" |
17 | #include "Poco/Net/PartHandler.h" |
18 | #include "Poco/Net/MultipartWriter.h" |
19 | #include "Poco/Net/MultipartReader.h" |
20 | #include "Poco/Net/NullPartHandler.h" |
21 | #include "Poco/Net/NetException.h" |
22 | #include "Poco/NullStream.h" |
23 | #include "Poco/CountingStream.h" |
24 | #include "Poco/StreamCopier.h" |
25 | #include "Poco/URI.h" |
26 | #include "Poco/String.h" |
27 | #include "Poco/CountingStream.h" |
28 | #include "Poco/UTF8String.h" |
29 | #include <sstream> |
30 | |
31 | |
32 | using Poco::NullInputStream; |
33 | using Poco::StreamCopier; |
34 | using Poco::SyntaxException; |
35 | using Poco::URI; |
36 | using Poco::icompare; |
37 | |
38 | |
39 | namespace Poco { |
40 | namespace Net { |
41 | |
42 | |
43 | HTMLForm::Part::Part(const std::string name, PartSource* pSource) : |
44 | _name(name), _pSource(pSource) |
45 | { |
46 | } |
47 | |
48 | |
49 | HTMLForm::Part::Part(Part&& other) : _name(std::move(other._name)), _pSource(other._pSource) |
50 | { |
51 | other._pSource = 0; |
52 | } |
53 | |
54 | |
55 | HTMLForm::Part::~Part() |
56 | { |
57 | delete _pSource; |
58 | } |
59 | |
60 | |
61 | const std::string HTMLForm::ENCODING_URL = "application/x-www-form-urlencoded" ; |
62 | const std::string HTMLForm::ENCODING_MULTIPART = "multipart/form-data" ; |
63 | const int HTMLForm::UNKNOWN_CONTENT_LENGTH = -1; |
64 | |
65 | |
66 | class HTMLFormCountingOutputStream: public CountingOutputStream |
67 | { |
68 | public: |
69 | HTMLFormCountingOutputStream(): |
70 | _valid(true) |
71 | { |
72 | } |
73 | |
74 | bool isValid() const |
75 | { |
76 | return _valid; |
77 | } |
78 | |
79 | void setValid(bool v) |
80 | { |
81 | _valid = v; |
82 | } |
83 | |
84 | private: |
85 | bool _valid; |
86 | }; |
87 | |
88 | |
89 | HTMLForm::HTMLForm(): |
90 | _fieldLimit(DFL_FIELD_LIMIT), |
91 | _valueLengthLimit(DFL_MAX_VALUE_LENGTH), |
92 | _encoding(ENCODING_URL) |
93 | { |
94 | } |
95 | |
96 | |
97 | HTMLForm::HTMLForm(const std::string& encoding): |
98 | _fieldLimit(DFL_FIELD_LIMIT), |
99 | _valueLengthLimit(DFL_MAX_VALUE_LENGTH), |
100 | _encoding(encoding) |
101 | { |
102 | } |
103 | |
104 | |
105 | HTMLForm::HTMLForm(const HTTPRequest& request, std::istream& requestBody, PartHandler& handler): |
106 | _fieldLimit(DFL_FIELD_LIMIT), |
107 | _valueLengthLimit(DFL_MAX_VALUE_LENGTH) |
108 | { |
109 | load(request, requestBody, handler); |
110 | } |
111 | |
112 | |
113 | HTMLForm::HTMLForm(const HTTPRequest& request, std::istream& requestBody): |
114 | _fieldLimit(DFL_FIELD_LIMIT), |
115 | _valueLengthLimit(DFL_MAX_VALUE_LENGTH) |
116 | { |
117 | load(request, requestBody); |
118 | } |
119 | |
120 | |
121 | HTMLForm::HTMLForm(const HTTPRequest& request): |
122 | _fieldLimit(DFL_FIELD_LIMIT), |
123 | _valueLengthLimit(DFL_MAX_VALUE_LENGTH) |
124 | { |
125 | load(request); |
126 | } |
127 | |
128 | |
129 | HTMLForm::~HTMLForm() |
130 | { |
131 | } |
132 | |
133 | |
134 | void HTMLForm::setEncoding(const std::string& encoding) |
135 | { |
136 | _encoding = encoding; |
137 | } |
138 | |
139 | |
140 | void HTMLForm::addPart(const std::string& name, PartSource* pSource) |
141 | { |
142 | poco_check_ptr (pSource); |
143 | _parts.push_back(Part(name, pSource)); |
144 | } |
145 | |
146 | |
147 | void HTMLForm::load(const HTTPRequest& request, std::istream& requestBody, PartHandler& handler) |
148 | { |
149 | clear(); |
150 | |
151 | URI uri(request.getURI()); |
152 | const std::string& query = uri.getRawQuery(); |
153 | if (!query.empty()) |
154 | { |
155 | std::istringstream istr(query); |
156 | readUrl(istr); |
157 | } |
158 | |
159 | if (request.getMethod() == HTTPRequest::HTTP_POST || request.getMethod() == HTTPRequest::HTTP_PUT) |
160 | { |
161 | std::string mediaType; |
162 | NameValueCollection params; |
163 | MessageHeader::splitParameters(request.getContentType(), mediaType, params); |
164 | _encoding = mediaType; |
165 | if (_encoding == ENCODING_MULTIPART) |
166 | { |
167 | _boundary = params["boundary" ]; |
168 | readMultipart(requestBody, handler); |
169 | } |
170 | else |
171 | { |
172 | readUrl(requestBody); |
173 | } |
174 | } |
175 | } |
176 | |
177 | |
178 | void HTMLForm::load(const HTTPRequest& request, std::istream& requestBody) |
179 | { |
180 | NullPartHandler nah; |
181 | load(request, requestBody, nah); |
182 | } |
183 | |
184 | |
185 | void HTMLForm::load(const HTTPRequest& request) |
186 | { |
187 | NullPartHandler nah; |
188 | NullInputStream nis; |
189 | load(request, nis, nah); |
190 | } |
191 | |
192 | |
193 | void HTMLForm::read(std::istream& istr, PartHandler& handler) |
194 | { |
195 | if (_encoding == ENCODING_URL) |
196 | readUrl(istr); |
197 | else |
198 | readMultipart(istr, handler); |
199 | } |
200 | |
201 | |
202 | void HTMLForm::read(std::istream& istr) |
203 | { |
204 | readUrl(istr); |
205 | } |
206 | |
207 | |
208 | void HTMLForm::read(const std::string& queryString) |
209 | { |
210 | std::istringstream istr(queryString); |
211 | readUrl(istr); |
212 | } |
213 | |
214 | |
215 | void HTMLForm::prepareSubmit(HTTPRequest& request, int options) |
216 | { |
217 | if (request.getMethod() == HTTPRequest::HTTP_POST || request.getMethod() == HTTPRequest::HTTP_PUT) |
218 | { |
219 | if (_encoding == ENCODING_URL) |
220 | { |
221 | request.setContentType(_encoding); |
222 | request.setChunkedTransferEncoding(false); |
223 | Poco::CountingOutputStream ostr; |
224 | writeUrl(ostr); |
225 | request.setContentLength(ostr.chars()); |
226 | } |
227 | else |
228 | { |
229 | _boundary = MultipartWriter::createBoundary(); |
230 | std::string ct(_encoding); |
231 | ct.append("; boundary=\"" ); |
232 | ct.append(_boundary); |
233 | ct.append("\"" ); |
234 | request.setContentType(ct); |
235 | } |
236 | if (request.getVersion() == HTTPMessage::HTTP_1_0) |
237 | { |
238 | request.setKeepAlive(false); |
239 | request.setChunkedTransferEncoding(false); |
240 | } |
241 | else if (_encoding != ENCODING_URL && (options & OPT_USE_CONTENT_LENGTH) == 0) |
242 | { |
243 | request.setChunkedTransferEncoding(true); |
244 | } |
245 | if (!request.getChunkedTransferEncoding() && !request.hasContentLength()) |
246 | { |
247 | request.setContentLength(calculateContentLength()); |
248 | } |
249 | } |
250 | else |
251 | { |
252 | std::string uri = request.getURI(); |
253 | std::ostringstream ostr; |
254 | writeUrl(ostr); |
255 | uri.append("?" ); |
256 | uri.append(ostr.str()); |
257 | request.setURI(uri); |
258 | } |
259 | } |
260 | |
261 | |
262 | std::streamsize HTMLForm::calculateContentLength() |
263 | { |
264 | if (_encoding == ENCODING_MULTIPART && _boundary.empty()) |
265 | throw HTMLFormException("Form must be prepared" ); |
266 | |
267 | HTMLFormCountingOutputStream c; |
268 | write(c); |
269 | if (c.isValid()) |
270 | return c.chars(); |
271 | else |
272 | return UNKNOWN_CONTENT_LENGTH; |
273 | } |
274 | |
275 | |
276 | void HTMLForm::write(std::ostream& ostr, const std::string& boundary) |
277 | { |
278 | if (_encoding == ENCODING_URL) |
279 | { |
280 | writeUrl(ostr); |
281 | } |
282 | else |
283 | { |
284 | _boundary = boundary; |
285 | writeMultipart(ostr); |
286 | } |
287 | } |
288 | |
289 | |
290 | void HTMLForm::write(std::ostream& ostr) |
291 | { |
292 | if (_encoding == ENCODING_URL) |
293 | writeUrl(ostr); |
294 | else |
295 | writeMultipart(ostr); |
296 | } |
297 | |
298 | |
299 | void HTMLForm::readUrl(std::istream& istr) |
300 | { |
301 | static const int eof = std::char_traits<char>::eof(); |
302 | |
303 | int fields = 0; |
304 | int ch = istr.get(); |
305 | bool isFirst = true; |
306 | while (ch != eof) |
307 | { |
308 | if (_fieldLimit > 0 && fields == _fieldLimit) |
309 | throw HTMLFormException("Too many form fields" ); |
310 | std::string name; |
311 | std::string value; |
312 | while (ch != eof && ch != '=' && ch != '&') |
313 | { |
314 | if (ch == '+') ch = ' '; |
315 | if (name.size() < MAX_NAME_LENGTH) |
316 | name += (char) ch; |
317 | else |
318 | throw HTMLFormException("Field name too long" ); |
319 | ch = istr.get(); |
320 | } |
321 | if (ch == '=') |
322 | { |
323 | ch = istr.get(); |
324 | while (ch != eof && ch != '&') |
325 | { |
326 | if (ch == '+') ch = ' '; |
327 | if (value.size() < _valueLengthLimit) |
328 | value += (char) ch; |
329 | else |
330 | throw HTMLFormException("Field value too long" ); |
331 | ch = istr.get(); |
332 | } |
333 | } |
334 | // remove UTF-8 byte order mark from first name, if present |
335 | if (isFirst) |
336 | { |
337 | UTF8::removeBOM(name); |
338 | } |
339 | std::string decodedName; |
340 | std::string decodedValue; |
341 | URI::decode(name, decodedName); |
342 | URI::decode(value, decodedValue); |
343 | add(decodedName, decodedValue); |
344 | ++fields; |
345 | if (ch == '&') ch = istr.get(); |
346 | isFirst = false; |
347 | } |
348 | } |
349 | |
350 | |
351 | void HTMLForm::readMultipart(std::istream& istr, PartHandler& handler) |
352 | { |
353 | static const int eof = std::char_traits<char>::eof(); |
354 | |
355 | int fields = 0; |
356 | MultipartReader reader(istr, _boundary); |
357 | while (reader.hasNextPart()) |
358 | { |
359 | if (_fieldLimit > 0 && fields == _fieldLimit) |
360 | throw HTMLFormException("Too many form fields" ); |
361 | MessageHeader ; |
362 | reader.nextPart(header); |
363 | std::string disp; |
364 | NameValueCollection params; |
365 | if (header.has("Content-Disposition" )) |
366 | { |
367 | std::string cd = header.get("Content-Disposition" ); |
368 | MessageHeader::splitParameters(cd, disp, params); |
369 | } |
370 | if (params.has("filename" )) |
371 | { |
372 | handler.handlePart(header, reader.stream()); |
373 | // Ensure that the complete part has been read. |
374 | while (reader.stream().good()) reader.stream().get(); |
375 | } |
376 | else |
377 | { |
378 | std::string name = params["name" ]; |
379 | std::string value; |
380 | std::istream& input = reader.stream(); |
381 | int ch = istr.get(); |
382 | while (ch != eof) |
383 | { |
384 | if (value.size() < _valueLengthLimit) |
385 | value += (char) ch; |
386 | else |
387 | throw HTMLFormException("Field value too long" ); |
388 | ch = input.get(); |
389 | } |
390 | add(name, value); |
391 | } |
392 | ++fields; |
393 | } |
394 | } |
395 | |
396 | |
397 | void HTMLForm::writeUrl(std::ostream& ostr) |
398 | { |
399 | for (NameValueCollection::ConstIterator it = begin(); it != end(); ++it) |
400 | { |
401 | if (it != begin()) ostr << "&" ; |
402 | std::string name; |
403 | URI::encode(it->first, "!?#/'\",;:$&()[]*+=@" , name); |
404 | std::string value; |
405 | URI::encode(it->second, "!?#/'\",;:$&()[]*+=@" , value); |
406 | ostr << name << "=" << value; |
407 | } |
408 | } |
409 | |
410 | |
411 | void HTMLForm::writeMultipart(std::ostream& ostr) |
412 | { |
413 | HTMLFormCountingOutputStream* pCountingOutputStream(dynamic_cast<HTMLFormCountingOutputStream*>(&ostr)); |
414 | |
415 | MultipartWriter writer(ostr, _boundary); |
416 | for (NameValueCollection::ConstIterator it = begin(); it != end(); ++it) |
417 | { |
418 | MessageHeader ; |
419 | std::string disp("form-data; name=\"" ); |
420 | disp.append(it->first); |
421 | disp.append("\"" ); |
422 | header.set("Content-Disposition" , disp); |
423 | writer.nextPart(header); |
424 | ostr << it->second; |
425 | } |
426 | for (PartVec::iterator ita = _parts.begin(); ita != _parts.end(); ++ita) |
427 | { |
428 | MessageHeader (ita->headers()); |
429 | std::string disp("form-data; name=\"" ); |
430 | disp.append(ita->name()); |
431 | disp.append("\"" ); |
432 | std::string filename = ita->filename(); |
433 | if (!filename.empty()) |
434 | { |
435 | disp.append("; filename=\"" ); |
436 | disp.append(filename); |
437 | disp.append("\"" ); |
438 | } |
439 | header.set("Content-Disposition" , disp); |
440 | header.set("Content-Type" , ita->mediaType()); |
441 | writer.nextPart(header); |
442 | if (pCountingOutputStream) |
443 | { |
444 | // count only, don't move stream position |
445 | std::streamsize partlen = ita->source()->getContentLength(); |
446 | if (partlen != PartSource::UNKNOWN_CONTENT_LENGTH) |
447 | pCountingOutputStream->addChars(static_cast<int>(partlen)); |
448 | else |
449 | pCountingOutputStream->setValid(false); |
450 | } |
451 | else |
452 | { |
453 | StreamCopier::copyStream(ita->stream(), ostr); |
454 | } |
455 | } |
456 | writer.close(); |
457 | _boundary = writer.boundary(); |
458 | } |
459 | |
460 | |
461 | void HTMLForm::setFieldLimit(int limit) |
462 | { |
463 | poco_assert (limit >= 0); |
464 | |
465 | _fieldLimit = limit; |
466 | } |
467 | |
468 | |
469 | void HTMLForm::setValueLengthLimit(int limit) |
470 | { |
471 | poco_assert (limit >= 0); |
472 | |
473 | _valueLengthLimit = limit; |
474 | } |
475 | |
476 | |
477 | } } // namespace Poco::Net |
478 | |