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
32using Poco::NullInputStream;
33using Poco::StreamCopier;
34using Poco::SyntaxException;
35using Poco::URI;
36using Poco::icompare;
37
38
39namespace Poco {
40namespace Net {
41
42
43HTMLForm::Part::Part(const std::string name, PartSource* pSource) :
44 _name(name), _pSource(pSource)
45{
46}
47
48
49HTMLForm::Part::Part(Part&& other) : _name(std::move(other._name)), _pSource(other._pSource)
50{
51 other._pSource = 0;
52}
53
54
55HTMLForm::Part::~Part()
56{
57 delete _pSource;
58}
59
60
61const std::string HTMLForm::ENCODING_URL = "application/x-www-form-urlencoded";
62const std::string HTMLForm::ENCODING_MULTIPART = "multipart/form-data";
63const int HTMLForm::UNKNOWN_CONTENT_LENGTH = -1;
64
65
66class HTMLFormCountingOutputStream: public CountingOutputStream
67{
68public:
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
84private:
85 bool _valid;
86};
87
88
89HTMLForm::HTMLForm():
90 _fieldLimit(DFL_FIELD_LIMIT),
91 _valueLengthLimit(DFL_MAX_VALUE_LENGTH),
92 _encoding(ENCODING_URL)
93{
94}
95
96
97HTMLForm::HTMLForm(const std::string& encoding):
98 _fieldLimit(DFL_FIELD_LIMIT),
99 _valueLengthLimit(DFL_MAX_VALUE_LENGTH),
100 _encoding(encoding)
101{
102}
103
104
105HTMLForm::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
113HTMLForm::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
121HTMLForm::HTMLForm(const HTTPRequest& request):
122 _fieldLimit(DFL_FIELD_LIMIT),
123 _valueLengthLimit(DFL_MAX_VALUE_LENGTH)
124{
125 load(request);
126}
127
128
129HTMLForm::~HTMLForm()
130{
131}
132
133
134void HTMLForm::setEncoding(const std::string& encoding)
135{
136 _encoding = encoding;
137}
138
139
140void HTMLForm::addPart(const std::string& name, PartSource* pSource)
141{
142 poco_check_ptr (pSource);
143 _parts.push_back(Part(name, pSource));
144}
145
146
147void 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
178void HTMLForm::load(const HTTPRequest& request, std::istream& requestBody)
179{
180 NullPartHandler nah;
181 load(request, requestBody, nah);
182}
183
184
185void HTMLForm::load(const HTTPRequest& request)
186{
187 NullPartHandler nah;
188 NullInputStream nis;
189 load(request, nis, nah);
190}
191
192
193void 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
202void HTMLForm::read(std::istream& istr)
203{
204 readUrl(istr);
205}
206
207
208void HTMLForm::read(const std::string& queryString)
209{
210 std::istringstream istr(queryString);
211 readUrl(istr);
212}
213
214
215void 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
262std::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
276void 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
290void HTMLForm::write(std::ostream& ostr)
291{
292 if (_encoding == ENCODING_URL)
293 writeUrl(ostr);
294 else
295 writeMultipart(ostr);
296}
297
298
299void 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
351void 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 header;
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
397void 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
411void 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 header;
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 header(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
461void HTMLForm::setFieldLimit(int limit)
462{
463 poco_assert (limit >= 0);
464
465 _fieldLimit = limit;
466}
467
468
469void HTMLForm::setValueLengthLimit(int limit)
470{
471 poco_assert (limit >= 0);
472
473 _valueLengthLimit = limit;
474}
475
476
477} } // namespace Poco::Net
478