// // HTMLForm.cpp // // $Id: //poco/1.4/Net/src/HTMLForm.cpp#4 $ // // Library: Net // Package: HTML // Module: HTMLForm // // Copyright (c) 2005-2006, Applied Informatics Software Engineering GmbH. // and Contributors. // // SPDX-License-Identifier: BSL-1.0 // #include "Poco/Net/HTMLForm.h" #include "Poco/Net/HTTPRequest.h" #include "Poco/Net/PartSource.h" #include "Poco/Net/PartHandler.h" #include "Poco/Net/MultipartWriter.h" #include "Poco/Net/MultipartReader.h" #include "Poco/Net/NullPartHandler.h" #include "Poco/Net/NetException.h" #include "Poco/NullStream.h" #include "Poco/CountingStream.h" #include "Poco/StreamCopier.h" #include "Poco/URI.h" #include "Poco/String.h" #include "Poco/CountingStream.h" #include "Poco/UTF8String.h" #include using Poco::NullInputStream; using Poco::StreamCopier; using Poco::SyntaxException; using Poco::URI; using Poco::icompare; namespace Poco { namespace Net { const std::string HTMLForm::ENCODING_URL = "application/x-www-form-urlencoded"; const std::string HTMLForm::ENCODING_MULTIPART = "multipart/form-data"; const int HTMLForm::UNKNOWN_CONTENT_LENGTH = -1; class HTMLFormCountingOutputStream: public CountingOutputStream { public: HTMLFormCountingOutputStream(): _valid(true) { } bool isValid() const { return _valid; } void setValid(bool v) { _valid = v; } private: bool _valid; }; HTMLForm::HTMLForm(): _fieldLimit(DFL_FIELD_LIMIT), _encoding(ENCODING_URL) { } HTMLForm::HTMLForm(const std::string& encoding): _fieldLimit(DFL_FIELD_LIMIT), _encoding(encoding) { } HTMLForm::HTMLForm(const HTTPRequest& request, std::istream& requestBody, PartHandler& handler): _fieldLimit(DFL_FIELD_LIMIT) { load(request, requestBody, handler); } HTMLForm::HTMLForm(const HTTPRequest& request, std::istream& requestBody): _fieldLimit(DFL_FIELD_LIMIT) { load(request, requestBody); } HTMLForm::HTMLForm(const HTTPRequest& request): _fieldLimit(DFL_FIELD_LIMIT) { load(request); } HTMLForm::~HTMLForm() { for (PartVec::iterator it = _parts.begin(); it != _parts.end(); ++it) { delete it->pSource; } } void HTMLForm::setEncoding(const std::string& encoding) { _encoding = encoding; } void HTMLForm::addPart(const std::string& name, PartSource* pSource) { poco_check_ptr (pSource); Part part; part.name = name; part.pSource = pSource; _parts.push_back(part); } void HTMLForm::load(const HTTPRequest& request, std::istream& requestBody, PartHandler& handler) { clear(); URI uri(request.getURI()); const std::string& query = uri.getRawQuery(); if (!query.empty()) { std::istringstream istr(query); readUrl(istr); } if (request.getMethod() == HTTPRequest::HTTP_POST || request.getMethod() == HTTPRequest::HTTP_PUT) { std::string mediaType; NameValueCollection params; MessageHeader::splitParameters(request.getContentType(), mediaType, params); _encoding = mediaType; if (_encoding == ENCODING_MULTIPART) { _boundary = params["boundary"]; readMultipart(requestBody, handler); } else { readUrl(requestBody); } } } void HTMLForm::load(const HTTPRequest& request, std::istream& requestBody) { NullPartHandler nah; load(request, requestBody, nah); } void HTMLForm::load(const HTTPRequest& request) { NullPartHandler nah; NullInputStream nis; load(request, nis, nah); } void HTMLForm::read(std::istream& istr, PartHandler& handler) { if (_encoding == ENCODING_URL) readUrl(istr); else readMultipart(istr, handler); } void HTMLForm::read(std::istream& istr) { readUrl(istr); } void HTMLForm::read(const std::string& queryString) { std::istringstream istr(queryString); readUrl(istr); } void HTMLForm::prepareSubmit(HTTPRequest& request) { if (request.getMethod() == HTTPRequest::HTTP_POST || request.getMethod() == HTTPRequest::HTTP_PUT) { if (_encoding == ENCODING_URL) { request.setContentType(_encoding); request.setChunkedTransferEncoding(false); Poco::CountingOutputStream ostr; writeUrl(ostr); request.setContentLength(ostr.chars()); } else { _boundary = MultipartWriter::createBoundary(); std::string ct(_encoding); ct.append("; boundary=\""); ct.append(_boundary); ct.append("\""); request.setContentType(ct); } if (request.getVersion() == HTTPMessage::HTTP_1_0) { request.setKeepAlive(false); request.setChunkedTransferEncoding(false); } else if (_encoding != ENCODING_URL) { request.setChunkedTransferEncoding(true); } } else { std::string uri = request.getURI(); std::ostringstream ostr; writeUrl(ostr); uri.append("?"); uri.append(ostr.str()); request.setURI(uri); } } std::streamsize HTMLForm::calculateContentLength() { if (_boundary.empty()) throw HTMLFormException("Form must be prepared"); HTMLFormCountingOutputStream c; write(c); if (c.isValid()) return c.chars(); else return UNKNOWN_CONTENT_LENGTH; } void HTMLForm::write(std::ostream& ostr, const std::string& boundary) { if (_encoding == ENCODING_URL) { writeUrl(ostr); } else { _boundary = boundary; writeMultipart(ostr); } } void HTMLForm::write(std::ostream& ostr) { if (_encoding == ENCODING_URL) writeUrl(ostr); else writeMultipart(ostr); } void HTMLForm::readUrl(std::istream& istr) { static const int eof = std::char_traits::eof(); int fields = 0; int ch = istr.get(); bool isFirst = true; while (ch != eof) { if (_fieldLimit > 0 && fields == _fieldLimit) throw HTMLFormException("Too many form fields"); std::string name; std::string value; while (ch != eof && ch != '=' && ch != '&') { if (ch == '+') ch = ' '; name += (char) ch; ch = istr.get(); } if (ch == '=') { ch = istr.get(); while (ch != eof && ch != '&') { if (ch == '+') ch = ' '; value += (char) ch; ch = istr.get(); } } // remove UTF-8 byte order mark from first name, if present if (isFirst) { UTF8::removeBOM(name); } std::string decodedName; std::string decodedValue; URI::decode(name, decodedName); URI::decode(value, decodedValue); add(decodedName, decodedValue); ++fields; if (ch == '&') ch = istr.get(); isFirst = false; } } void HTMLForm::readMultipart(std::istream& istr, PartHandler& handler) { static const int eof = std::char_traits::eof(); int fields = 0; MultipartReader reader(istr, _boundary); while (reader.hasNextPart()) { if (_fieldLimit > 0 && fields == _fieldLimit) throw HTMLFormException("Too many form fields"); MessageHeader header; reader.nextPart(header); std::string disp; NameValueCollection params; if (header.has("Content-Disposition")) { std::string cd = header.get("Content-Disposition"); MessageHeader::splitParameters(cd, disp, params); } if (params.has("filename")) { handler.handlePart(header, reader.stream()); // Ensure that the complete part has been read. while (reader.stream().good()) reader.stream().get(); } else { std::string name = params["name"]; std::string value; std::istream& istr = reader.stream(); int ch = istr.get(); while (ch != eof) { value += (char) ch; ch = istr.get(); } add(name, value); } ++fields; } } void HTMLForm::writeUrl(std::ostream& ostr) { for (NameValueCollection::ConstIterator it = begin(); it != end(); ++it) { if (it != begin()) ostr << "&"; std::string name; URI::encode(it->first, "!?#/'\",;:$&()[]*+=@", name); std::string value; URI::encode(it->second, "!?#/'\",;:$&()[]*+=@", value); ostr << name << "=" << value; } } void HTMLForm::writeMultipart(std::ostream& ostr) { HTMLFormCountingOutputStream *costr(dynamic_cast(&ostr)); MultipartWriter writer(ostr, _boundary); for (NameValueCollection::ConstIterator it = begin(); it != end(); ++it) { MessageHeader header; std::string disp("form-data; name=\""); disp.append(it->first); disp.append("\""); header.set("Content-Disposition", disp); writer.nextPart(header); ostr << it->second; } for (PartVec::iterator ita = _parts.begin(); ita != _parts.end(); ++ita) { MessageHeader header(ita->pSource->headers()); std::string disp("form-data; name=\""); disp.append(ita->name); disp.append("\""); std::string filename = ita->pSource->filename(); if (!filename.empty()) { disp.append("; filename=\""); disp.append(filename); disp.append("\""); } header.set("Content-Disposition", disp); header.set("Content-Type", ita->pSource->mediaType()); writer.nextPart(header); if (costr) { // count only, don't move stream position std::streamsize partlen = ita->pSource->getContentLength(); if (partlen != PartSource::UNKNOWN_CONTENT_LENGTH) costr->addChars(static_cast(partlen)); else costr->setValid(false); } else StreamCopier::copyStream(ita->pSource->stream(), ostr); } writer.close(); _boundary = writer.boundary(); } void HTMLForm::setFieldLimit(int limit) { poco_assert (limit >= 0); _fieldLimit = limit; } } } // namespace Poco::Net