From cc4f27d41ee5c15954f41d820a0d012c7d594d4f Mon Sep 17 00:00:00 2001 From: lganzzzo Date: Wed, 17 Jul 2019 04:40:42 +0300 Subject: [PATCH] Introduce Multipart InMemoryReader --- src/CMakeLists.txt | 4 + .../codegen/codegen_define_ApiController_.hpp | 2 +- src/oatpp/core/data/stream/Stream.cpp | 13 +++ src/oatpp/core/data/stream/Stream.hpp | 34 ++++++ .../web/mime/multipart/InMemoryReader.cpp | 85 ++++++++++++++ .../web/mime/multipart/InMemoryReader.hpp | 98 +++++++++++++++++ src/oatpp/web/mime/multipart/Multipart.cpp | 94 ++++++++++++++++ src/oatpp/web/mime/multipart/Multipart.hpp | 104 ++++++++++++++++++ src/oatpp/web/mime/multipart/Part.cpp | 47 +------- src/oatpp/web/mime/multipart/Part.hpp | 7 +- .../web/mime/multipart/StatefulParser.cpp | 42 +------ .../web/mime/multipart/StatefulParser.hpp | 19 ++-- src/oatpp/web/protocol/http/Http.cpp | 56 ++++++++-- src/oatpp/web/protocol/http/Http.hpp | 36 +++++- test/oatpp/web/app/Controller.hpp | 36 +++++- .../web/mime/multipart/StatefulParserTest.cpp | 49 ++------- 16 files changed, 570 insertions(+), 156 deletions(-) create mode 100644 src/oatpp/web/mime/multipart/InMemoryReader.cpp create mode 100644 src/oatpp/web/mime/multipart/InMemoryReader.hpp create mode 100644 src/oatpp/web/mime/multipart/Multipart.cpp create mode 100644 src/oatpp/web/mime/multipart/Multipart.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 55323cb5..0305b686 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -141,6 +141,10 @@ add_library(oatpp oatpp/web/client/HttpRequestExecutor.hpp oatpp/web/client/RequestExecutor.cpp oatpp/web/client/RequestExecutor.hpp + oatpp/web/mime/multipart/InMemoryReader.cpp + oatpp/web/mime/multipart/InMemoryReader.hpp + oatpp/web/mime/multipart/Multipart.cpp + oatpp/web/mime/multipart/Multipart.hpp oatpp/web/mime/multipart/Part.cpp oatpp/web/mime/multipart/Part.hpp oatpp/web/mime/multipart/StatefulParser.cpp diff --git a/src/oatpp/codegen/codegen_define_ApiController_.hpp b/src/oatpp/codegen/codegen_define_ApiController_.hpp index 2cd304da..d81a7e1b 100644 --- a/src/oatpp/codegen/codegen_define_ApiController_.hpp +++ b/src/oatpp/codegen/codegen_define_ApiController_.hpp @@ -267,7 +267,7 @@ info->body.type = oatpp::data::mapping::type::__class::String::getType(); #define OATPP_MACRO_API_CONTROLLER_BODY_DTO(TYPE, NAME, PARAM_LIST) \ TYPE NAME; \ -__request->readBodyToDto(NAME, getDefaultObjectMapper()); \ +__request->readBodyToDto(NAME, getDefaultObjectMapper().get()); \ if(!NAME) { \ return ApiController::handleError(Status::CODE_400, "Missing valid body parameter '" #NAME "'"); \ } diff --git a/src/oatpp/core/data/stream/Stream.cpp b/src/oatpp/core/data/stream/Stream.cpp index 5b6aa613..31d75ae5 100644 --- a/src/oatpp/core/data/stream/Stream.cpp +++ b/src/oatpp/core/data/stream/Stream.cpp @@ -90,6 +90,19 @@ data::v_io_size ConsistentOutputStream::writeAsString(bool value) { } } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// AsyncWriteCallbackWithCoroutineStarter + +oatpp::async::Action AsyncWriteCallbackWithCoroutineStarter::writeAsyncInline(oatpp::async::AbstractCoroutine* coroutine, + const void*& currBufferPtr, + data::v_io_size& bytesLeft, + oatpp::async::Action&& nextAction) +{ + auto coroutineStarter = writeAsync(currBufferPtr, bytesLeft); + currBufferPtr = &((p_char8) currBufferPtr)[bytesLeft]; + bytesLeft = 0; + return coroutineStarter.next(std::forward(nextAction)); +} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // DefaultWriteCallback diff --git a/src/oatpp/core/data/stream/Stream.hpp b/src/oatpp/core/data/stream/Stream.hpp index dead9041..8c7cde6e 100644 --- a/src/oatpp/core/data/stream/Stream.hpp +++ b/src/oatpp/core/data/stream/Stream.hpp @@ -335,6 +335,40 @@ public: oatpp::async::Action&& nextAction) = 0; }; +/** + * Convenience callback to use coroutine starter instead of async inline method. + */ +class AsyncWriteCallbackWithCoroutineStarter : public AsyncWriteCallback { +public: + + /** + * Async-Inline write callback.
+ * Calls &l:AsyncWriteCallbackWithCoroutineStarter::writeAsync (); internally. + * @param coroutine - caller coroutine. + * @param currBufferPtr - pointer to current data position. + * @param bytesLeft - how much bytes left to write. + * @param nextAction - next action when write finished. + * @return - &id:oatpp::async::Action;. + */ + oatpp::async::Action writeAsyncInline(oatpp::async::AbstractCoroutine* coroutine, + const void*& currBufferPtr, + data::v_io_size& bytesLeft, + oatpp::async::Action&& nextAction) override; + +public: + + /** + * Implement this method!
+ * Write Callback. Data should be fully utilized on call to this method - no partial writes for this method.
+ * Return Coroutine starter to start data-processing coroutine or, `nullptr` to do nothing. + * @param data - pointer to data. + * @param count - data size. + * @return - data processing Coroutine-Starter. &id:oatpp::async::CoroutineStarter;. + */ + virtual async::CoroutineStarter writeAsync(const void *data, data::v_io_size count) = 0; + +}; + /** * Default callback for stream write operation.
* Uses &l:writeExactSizeData (); method underhood. diff --git a/src/oatpp/web/mime/multipart/InMemoryReader.cpp b/src/oatpp/web/mime/multipart/InMemoryReader.cpp new file mode 100644 index 00000000..3d4dd6c4 --- /dev/null +++ b/src/oatpp/web/mime/multipart/InMemoryReader.cpp @@ -0,0 +1,85 @@ +/*************************************************************************** + * + * Project _____ __ ____ _ _ + * ( _ ) /__\ (_ _)_| |_ _| |_ + * )(_)( /(__)\ )( (_ _)(_ _) + * (_____)(__)(__)(__) |_| |_| + * + * + * Copyright 2018-present, Leonid Stryzhevskyi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************/ + +#include "InMemoryReader.hpp" + +#include "oatpp/core/data/stream/BufferInputStream.hpp" + +namespace oatpp { namespace web { namespace mime { namespace multipart { + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// InMemoryParser + +InMemoryParser::InMemoryParser(Multipart* multipart) + : m_multipart(multipart) +{} + +void InMemoryParser::onPartHeaders(const Headers& partHeaders) { + m_currPart = std::make_shared(partHeaders); +} + +void InMemoryParser::onPartData(p_char8 data, oatpp::data::v_io_size size) { + if(size > 0) { + m_buffer.write(data, size); + } else { + auto fullData = m_buffer.toString(); + m_buffer.clear(); + auto stream = std::make_shared(fullData.getPtr(), fullData->getData(), fullData->getSize()); + m_currPart->setDataInfo(stream, fullData, fullData->getSize()); + m_multipart->addPart(m_currPart); + m_currPart = nullptr; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// InMemoryReader + +InMemoryReader::InMemoryReader(Multipart* multipart) + : m_parser(multipart->getBoundary(), std::make_shared(multipart)) +{} + +data::v_io_size InMemoryReader::write(const void *data, data::v_io_size count) { + m_parser.parseNext((p_char8) data, count); + return count; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// AsyncInMemoryReader + +AsyncInMemoryReader::AsyncInMemoryReader(Multipart* multipart) + : m_parser(multipart->getBoundary(), std::make_shared(multipart)) +{} + +oatpp::async::Action AsyncInMemoryReader::writeAsyncInline(oatpp::async::AbstractCoroutine* coroutine, + const void*& currBufferPtr, + data::v_io_size& bytesLeft, + oatpp::async::Action&& nextAction) +{ + m_parser.parseNext((p_char8) currBufferPtr, bytesLeft); + currBufferPtr = &((p_char8) currBufferPtr)[bytesLeft]; + bytesLeft = 0; + return std::forward(nextAction); +} + +}}}} diff --git a/src/oatpp/web/mime/multipart/InMemoryReader.hpp b/src/oatpp/web/mime/multipart/InMemoryReader.hpp new file mode 100644 index 00000000..bc529dd9 --- /dev/null +++ b/src/oatpp/web/mime/multipart/InMemoryReader.hpp @@ -0,0 +1,98 @@ +/*************************************************************************** + * + * Project _____ __ ____ _ _ + * ( _ ) /__\ (_ _)_| |_ _| |_ + * )(_)( /(__)\ )( (_ _)(_ _) + * (_____)(__)(__)(__) |_| |_| + * + * + * Copyright 2018-present, Leonid Stryzhevskyi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************/ + +#ifndef oatpp_web_mime_multipart_InMemoryReader_hpp +#define oatpp_web_mime_multipart_InMemoryReader_hpp + +#include "Multipart.hpp" +#include "StatefulParser.hpp" +#include "oatpp/core/data/stream/ChunkedBuffer.hpp" + +namespace oatpp { namespace web { namespace mime { namespace multipart { + +/** + * In memory multipart parser.
+ * Extends - &id:oatpp::web::mime::multipart::StatefulParser::Listener;. + */ +class InMemoryParser : public StatefulParser::Listener { +private: + Multipart* m_multipart; + std::shared_ptr m_currPart; + data::stream::ChunkedBuffer m_buffer; +public: + + /** + * Constructor. + * @param multipart - pointer to &id:oatpp::web::mime::multipart::Multipart;. + */ + InMemoryParser(Multipart* multipart); + + void onPartHeaders(const Headers& partHeaders) override; + + void onPartData(p_char8 data, oatpp::data::v_io_size size) override; +}; + +/** + * In memory Multipart reader. + * Extends - &id:oatpp::data::stream::WriteCallback;. + */ +class InMemoryReader : public oatpp::data::stream::WriteCallback { +private: + StatefulParser m_parser; +public: + + /** + * Constructor. + * @param multipart - Multipart object to save read data to. + */ + InMemoryReader(Multipart* multipart); + + data::v_io_size write(const void *data, data::v_io_size count) override; + +}; + +/** + * In memory Multipart reader. + * Extends - &id:oatpp::data::stream::AsyncWriteCallback;. + */ +class AsyncInMemoryReader : public oatpp::data::stream::AsyncWriteCallback { +private: + StatefulParser m_parser; +public: + + /** + * Constructor. + * @param multipart - Multipart object to save read data to. + */ + AsyncInMemoryReader(Multipart* multipart); + + oatpp::async::Action writeAsyncInline(oatpp::async::AbstractCoroutine* coroutine, + const void*& currBufferPtr, + data::v_io_size& bytesLeft, + oatpp::async::Action&& nextAction) override; +}; + +}}}} + +#endif //oatpp_web_mime_multipart_InMemoryReader_hpp diff --git a/src/oatpp/web/mime/multipart/Multipart.cpp b/src/oatpp/web/mime/multipart/Multipart.cpp new file mode 100644 index 00000000..98b4f03d --- /dev/null +++ b/src/oatpp/web/mime/multipart/Multipart.cpp @@ -0,0 +1,94 @@ +/*************************************************************************** + * + * Project _____ __ ____ _ _ + * ( _ ) /__\ (_ _)_| |_ _| |_ + * )(_)( /(__)\ )( (_ _)(_ _) + * (_____)(__)(__)(__) |_| |_| + * + * + * Copyright 2018-present, Leonid Stryzhevskyi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************/ + +#include "Multipart.hpp" + +#include "oatpp/web/protocol/http/Http.hpp" + +namespace oatpp { namespace web { namespace mime { namespace multipart { + + +Multipart::Multipart(const oatpp::String& boundary) + : m_boundary(boundary) +{} + + +Multipart::Multipart(const Headers& requestHeaders){ + + auto it = requestHeaders.find("Content-Type"); + if(it != requestHeaders.end()) { + + oatpp::web::protocol::http::HeaderValueData valueData; + oatpp::web::protocol::http::Parser::parseHeaderValueData(valueData, it->second, ';'); + + m_boundary = valueData.getTitleParamValue("boundary"); + + if(!m_boundary) { + throw std::runtime_error("[oatpp::web::mime::multipart::Multipart::Multipart()]: Error. Boundary not defined."); + } + + } else { + throw std::runtime_error("[oatpp::web::mime::multipart::Multipart::Multipart()]: Error. 'Content-Type' header is missing."); + } + +} + +oatpp::String Multipart::getBoundary() { + return m_boundary; +} + +void Multipart::addPart(const std::shared_ptr& part) { + + if(part->getName()) { + auto it = m_namedParts.find(part->getName()); + if(it != m_namedParts.end()) { + throw std::runtime_error("[oatpp::web::mime::multipart::Multipart::addPart()]: Error. Part with such name already exists."); + } + m_namedParts.insert({part->getName(), part}); + } + + m_parts.push_back(part); + +} + +std::shared_ptr Multipart::getNamedPart(const oatpp::String& name) { + + auto it = m_namedParts.find(name); + if(it != m_namedParts.end()) { + return it->second; + } + + return nullptr; + +} + +const std::list>& Multipart::getAllParts() { + return m_parts; +} + +v_int32 Multipart::count() { + return m_parts.size(); +} + +}}}} \ No newline at end of file diff --git a/src/oatpp/web/mime/multipart/Multipart.hpp b/src/oatpp/web/mime/multipart/Multipart.hpp new file mode 100644 index 00000000..28aee4de --- /dev/null +++ b/src/oatpp/web/mime/multipart/Multipart.hpp @@ -0,0 +1,104 @@ +/*************************************************************************** + * + * Project _____ __ ____ _ _ + * ( _ ) /__\ (_ _)_| |_ _| |_ + * )(_)( /(__)\ )( (_ _)(_ _) + * (_____)(__)(__)(__) |_| |_| + * + * + * Copyright 2018-present, Leonid Stryzhevskyi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************/ + +#ifndef oatpp_web_mime_multipart_Multipart_hpp +#define oatpp_web_mime_multipart_Multipart_hpp + +#include "Part.hpp" +#include +#include + +namespace oatpp { namespace web { namespace mime { namespace multipart { + +/** + * Typedef for headers map. Headers map key is case-insensitive. + * `std::unordered_map` of &id:oatpp::data::share::StringKeyLabelCI_FAST; and &id:oatpp::data::share::StringKeyLabel;. + */ +typedef std::unordered_map Headers; + +/** + * Structure that holds parts of Multipart. + */ +class Multipart { +private: + oatpp::String m_boundary; + std::unordered_map> m_namedParts; + std::list> m_parts; +public: + + /** + * Constructor. + * @param boundary - multipart boundary value. + */ + Multipart(const oatpp::String& boundary); + + /** + * Constructor. + * @param requestHeaders - request headers. Headers must contain "Content-Type" header. + */ + Multipart(const Headers& requestHeaders); + + /** + * Default virtual Destructor. + */ + virtual ~Multipart() = default; + + /** + * Get multipart boundary value. + * @return - multipart boundary value. + */ + oatpp::String getBoundary(); + + /** + * Add part to Multipart. + * @param part - &id:oatpp::web::mime::multipart::Part;. + */ + void addPart(const std::shared_ptr& part); + + /** + * Get part by name
+ * Applicable to named parts only. + * @param name - &id:oatpp::String;. + * @return - &id:oatpp::web::mime::multipart::Part;. + */ + std::shared_ptr getNamedPart(const oatpp::String& name); + + /** + * Get list of all parts. + * @return - `std::list` of `std::shared_ptr` to &id:oatpp::web::mime::multipart::Part;. + */ + const std::list>& getAllParts(); + + /** + * Get parts count. + * @return - parts count. + */ + v_int32 count(); + +}; + +}}}} + + +#endif // oatpp_web_mime_multipart_Multipart_hpp diff --git a/src/oatpp/web/mime/multipart/Part.cpp b/src/oatpp/web/mime/multipart/Part.cpp index 43b7c0c8..b28cea77 100644 --- a/src/oatpp/web/mime/multipart/Part.cpp +++ b/src/oatpp/web/mime/multipart/Part.cpp @@ -24,46 +24,13 @@ #include "Part.hpp" +#include "oatpp/web/protocol/http/Http.hpp" #include "oatpp/core/parser/Caret.hpp" #include namespace oatpp { namespace web { namespace mime { namespace multipart { -oatpp::String Part::parseContentDispositionValue(const char* key, p_char8 data, v_int32 size) { - - parser::Caret caret(data, size); - - if(caret.findText(key)) { - - caret.inc(std::strlen(key)); - - parser::Caret::Label label(nullptr); - - if(caret.isAtChar('"')) { - label = caret.parseStringEnclosed('"', '"', '\\'); - } else if(caret.isAtChar('\'')) { - label = caret.parseStringEnclosed('\'', '\'', '\\'); - } else { - label = caret.putLabel(); - caret.findCharFromSet(" \t\n\r\f"); - label.end(); - } - - if(label) { - - return label.toString(); - - } else { - throw std::runtime_error("[oatpp::web::mime::multipart::Part::parseContentDispositionValue()]: Error. Can't parse value."); - } - - } - - return nullptr; - -} - Part::Part(const Headers &headers, const std::shared_ptr &inputStream, const oatpp::String inMemoryData, @@ -77,16 +44,12 @@ Part::Part(const Headers &headers, auto it = m_headers.find("Content-Disposition"); if(it != m_headers.end()) { - m_name = parseContentDispositionValue("name=", it->second.getData(), it->second.getSize()); + oatpp::web::protocol::http::HeaderValueData valueData; + oatpp::web::protocol::http::Parser::parseHeaderValueData(valueData, it->second, ';'); - if(!m_name) { - throw std::runtime_error("[oatpp::web::mime::multipart::Part::Part()]: Error. Part name is missing in 'Content-Disposition' header."); - } + m_name = valueData.getTitleParamValue("name"); + m_filename = valueData.getTitleParamValue("filename"); - m_filename = parseContentDispositionValue("filename=", it->second.getData(), it->second.getSize()); - - } else { - throw std::runtime_error("[oatpp::web::mime::multipart::Part::Part()]: Error. Missing 'Content-Disposition' header."); } } diff --git a/src/oatpp/web/mime/multipart/Part.hpp b/src/oatpp/web/mime/multipart/Part.hpp index b0801a2f..514ca6d8 100644 --- a/src/oatpp/web/mime/multipart/Part.hpp +++ b/src/oatpp/web/mime/multipart/Part.hpp @@ -42,11 +42,6 @@ public: * `std::unordered_map` of &id:oatpp::data::share::StringKeyLabelCI_FAST; and &id:oatpp::data::share::StringKeyLabel;. */ typedef std::unordered_map Headers; -private: - /** - * Parse value from the `Content-Disposition` header. - */ - static oatpp::String parseContentDispositionValue(const char* key, p_char8 data, v_int32 size); private: oatpp::String m_name; oatpp::String m_filename; @@ -97,7 +92,7 @@ public: oatpp::String getFilename() const; /** - * Get request's headers map + * Get request's headers map. * @return Headers map */ const Headers& getHeaders() const; diff --git a/src/oatpp/web/mime/multipart/StatefulParser.cpp b/src/oatpp/web/mime/multipart/StatefulParser.cpp index ea168435..ab114188 100644 --- a/src/oatpp/web/mime/multipart/StatefulParser.cpp +++ b/src/oatpp/web/mime/multipart/StatefulParser.cpp @@ -27,42 +27,12 @@ #include "oatpp/web/protocol/http/Http.hpp" #include "oatpp/core/parser/Caret.hpp" +#include "oatpp/core/parser/ParsingError.hpp" namespace oatpp { namespace web { namespace mime { namespace multipart { -oatpp::String StatefulParser::parsePartName(p_char8 data, v_int32 size) { - - parser::Caret caret(data, size); - - if(caret.findText((p_char8)"name=", 5)) { - - caret.inc(5); - - parser::Caret::Label nameLabel(nullptr); - - if(caret.isAtChar('"')) { - nameLabel = caret.parseStringEnclosed('"', '"', '\\'); - } else if(caret.isAtChar('\'')) { - nameLabel = caret.parseStringEnclosed('\'', '\'', '\\'); - } else { - nameLabel = caret.putLabel(); - caret.findCharFromSet(" \t\n\r\f"); - nameLabel.end(); - } - - if(nameLabel) { - - return nameLabel.toString(); - - } else { - throw std::runtime_error("[oatpp::web::mime::multipart::StatefulParser::parsePartName()]: Error. Can't parse part name."); - } - - } else { - throw std::runtime_error("[oatpp::web::mime::multipart::StatefulParser::parsePartName()]: Error. Part name is missing."); - } - -} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// StatefulParser StatefulParser::StatefulParser(const oatpp::String& boundary, const std::shared_ptr& listener) : m_state(STATE_BOUNDARY) @@ -85,10 +55,8 @@ void StatefulParser::onPartHeaders(const Headers& partHeaders) { auto it = partHeaders.find("Content-Disposition"); if(it != partHeaders.end()) { - m_currPartName = parsePartName(it->second.getData(), it->second.getSize()); - if(m_listener) { - m_listener->onPartHeaders(m_currPartName, partHeaders); + m_listener->onPartHeaders(partHeaders); } } else { @@ -100,7 +68,7 @@ void StatefulParser::onPartHeaders(const Headers& partHeaders) { void StatefulParser::onPartData(p_char8 data, v_int32 size) { if(m_listener) { - m_listener->onPartData(m_currPartName, data, size); + m_listener->onPartData(data, size); } } diff --git a/src/oatpp/web/mime/multipart/StatefulParser.hpp b/src/oatpp/web/mime/multipart/StatefulParser.hpp index 1de4c031..184fcfcd 100644 --- a/src/oatpp/web/mime/multipart/StatefulParser.hpp +++ b/src/oatpp/web/mime/multipart/StatefulParser.hpp @@ -52,11 +52,6 @@ private: * `std::unordered_map` of &id:oatpp::data::share::StringKeyLabelCI_FAST; and &id:oatpp::data::share::StringKeyLabel;. */ typedef std::unordered_map Headers; -private: - /** - * Parse name of the part from `Content-Disposition` header. - */ - static oatpp::String parsePartName(p_char8 data, v_int32 size); public: /** @@ -71,23 +66,26 @@ public: typedef std::unordered_map Headers; public: + /** + * Default virtual Destructor. + */ + virtual ~Listener() = default; + /** * Called on new part found in the stream. * Always called before `onPartData` events. - * @param name - name of the part. * @param partHeaders - complete set of part headers. */ - virtual void onPartHeaders(const oatpp::String& name, const Headers& partHeaders) = 0; + virtual void onPartHeaders(const Headers& partHeaders) = 0; /** * Called on each new chunk of bytes parsed from the part body. * When all data of message is read, readMessage is called again with size == 0 to - * indicate end of the message. - * @param name - name of the part. + * indicate end of the part. * @param data - pointer to data. * @param size - size of the data in bytes. */ - virtual void onPartData(const oatpp::String& name, p_char8 data, oatpp::data::v_io_size size) = 0; + virtual void onPartData(p_char8 data, oatpp::data::v_io_size size) = 0; }; @@ -104,7 +102,6 @@ private: oatpp::String m_firstBoundarySample; oatpp::String m_nextBoundarySample; - oatpp::String m_currPartName; /* * Headers of the part are stored in the buffer and are parsed as one chunk. diff --git a/src/oatpp/web/protocol/http/Http.cpp b/src/oatpp/web/protocol/http/Http.cpp index 0507d668..13cbd6ee 100644 --- a/src/oatpp/web/protocol/http/Http.cpp +++ b/src/oatpp/web/protocol/http/Http.cpp @@ -235,6 +235,15 @@ ContentRange ContentRange::parse(const oatpp::String& str) { oatpp::parser::Caret caret(str); return parse(caret); } + + +oatpp::String HeaderValueData::getTitleParamValue(const data::share::StringKeyLabelCI& key) const { + auto it = titleParams.find(key); + if(it != titleParams.end()) { + return it->second.toString(); + } + return nullptr; +} oatpp::data::share::StringKeyLabelCI_FAST Parser::parseHeaderNameLabel(const std::shared_ptr& headersText, oatpp::parser::Caret& caret) { @@ -351,20 +360,47 @@ void Parser::parseHeaders(Headers& headers, } -std::unordered_set Parser::parseHeaderValueSet(const oatpp::data::share::StringKeyLabel& headerValue, char separator) { +void Parser::parseHeaderValueData(HeaderValueData& data, const oatpp::data::share::StringKeyLabel& headerValue, v_char8 separator) { - std::unordered_set result; oatpp::parser::Caret caret(headerValue.getData(), headerValue.getSize()); - while (caret.canContinue()) { - caret.skipChar(' '); // skip leading space - auto label = caret.putLabel(); - caret.findChar(separator); // find separator char, or end of the header value - result.insert(oatpp::data::share::StringKeyLabelCI(headerValue.getMemoryHandle(), label.getData(), label.getSize())); - caret.inc(); - } + v_char8 charSet[5] = {' ', '=', separator, '\r', '\n'}; + v_char8 charSet2[4] = {' ', separator, '\r', '\n'}; - return result; + while(caret.canContinue()) { + + caret.skipChar(' '); + + auto label = caret.putLabel(); + auto res = caret.findCharFromSet(charSet, 5); + + if(res == '=') { + + data::share::StringKeyLabelCI key(headerValue.getMemoryHandle(), label.getData(), label.getSize()); + caret.inc(); + + if(caret.isAtChar('"')) { + label = caret.parseStringEnclosed('"', '"', '\\'); + } else if(caret.isAtChar('\'')) { + label = caret.parseStringEnclosed('\'', '\'', '\\'); + } else { + label = caret.putLabel(); + auto r = caret.findCharFromSet(charSet2, 4); + } + + data.titleParams[key] = data::share::StringKeyLabel(headerValue.getMemoryHandle(), label.getData(), label.getSize()); + + } else { + data.tokens.insert(data::share::StringKeyLabelCI(headerValue.getMemoryHandle(), label.getData(), label.getSize())); + } + + if(caret.isAtCharFromSet((p_char8)"\r\n", 2)) { + break; + } else if(caret.isAtChar(separator)) { + caret.inc(); + } + + } } diff --git a/src/oatpp/web/protocol/http/Http.hpp b/src/oatpp/web/protocol/http/Http.hpp index 58e42909..6fa41e81 100644 --- a/src/oatpp/web/protocol/http/Http.hpp +++ b/src/oatpp/web/protocol/http/Http.hpp @@ -578,6 +578,30 @@ struct ResponseStartingLine { oatpp::data::share::StringKeyLabel description; }; +/** + * Data contained in the value of one header. + */ +struct HeaderValueData { + + /** + * value tokens. + */ + std::unordered_set tokens; + + /** + * Title params. + */ + std::unordered_map titleParams; + + /** + * Get title parm value by key. + * @param key + * @return + */ + oatpp::String getTitleParamValue(const data::share::StringKeyLabelCI& key) const; + +}; + /** * Typedef for headers map. Headers map key is case-insensitive. * `std::unordered_map` of &id:oatpp::data::share::StringKeyLabelCI_FAST; and &id:oatpp::data::share::StringKeyLabel;. @@ -653,13 +677,13 @@ public: Status& error); /** - * Parse header value separated by `char separator`. - * @param headerValue - value of the header. - * @param separator - separator char. - * @return - `std::unordered_set` of &id:oatpp::data::share::StringKeyLabelCI;. + * Parse data that is contained in a one header. + * @param data - out. parsed data. + * @param headerValue - header value string. + * @param separator - subvalues separator. */ - static std::unordered_set parseHeaderValueSet(const oatpp::data::share::StringKeyLabel& headerValue, char separator); - + static void parseHeaderValueData(HeaderValueData& data, const oatpp::data::share::StringKeyLabel& headerValue, v_char8 separator); + }; }}}} diff --git a/test/oatpp/web/app/Controller.hpp b/test/oatpp/web/app/Controller.hpp index 1f0edabf..434142e7 100644 --- a/test/oatpp/web/app/Controller.hpp +++ b/test/oatpp/web/app/Controller.hpp @@ -26,6 +26,9 @@ #define oatpp_test_web_app_Controller_hpp #include "./DTOs.hpp" + +#include "oatpp/web/mime/multipart/InMemoryReader.hpp" + #include "oatpp/web/server/api/ApiController.hpp" #include "oatpp/parser/json/mapping/ObjectMapper.hpp" #include "oatpp/core/utils/ConversionUtils.hpp" @@ -97,6 +100,14 @@ public: return createDtoResponse(Status::CODE_200, dto); } + ENDPOINT("POST", "body-dto", postBodyDto, + BODY_DTO(TestDto::ObjectWrapper, body)) { + //OATPP_LOGV(TAG, "POST body %s", body->c_str()); + auto dto = TestDto::createShared(); + dto->testValue = body->testValue; + return createDtoResponse(Status::CODE_200, dto); + } + ENDPOINT("POST", "echo", echo, BODY_STRING(String, body)) { //OATPP_LOGV(TAG, "POST body(echo) size=%d", body->getSize()); @@ -105,13 +116,30 @@ public: ENDPOINT("GET", "header-value-set", headerValueSet, HEADER(String, valueSet, "X-VALUE-SET")) { - auto set = oatpp::web::protocol::http::Parser::parseHeaderValueSet(valueSet, ','); - OATPP_ASSERT_HTTP(set.find("VALUE_1") != set.end(), Status::CODE_400, "No header 'VALUE_1' in value set"); - OATPP_ASSERT_HTTP(set.find("VALUE_2") != set.end(), Status::CODE_400, "No header 'VALUE_2' in value set"); - OATPP_ASSERT_HTTP(set.find("VALUE_3") != set.end(), Status::CODE_400, "No header 'VALUE_3' in value set"); + + oatpp::web::protocol::http::HeaderValueData valueData; + oatpp::web::protocol::http::Parser::parseHeaderValueData(valueData, valueSet, ','); + + OATPP_ASSERT_HTTP(valueData.tokens.find("VALUE_1") != valueData.tokens.end(), Status::CODE_400, "No header 'VALUE_1' in value set"); + OATPP_ASSERT_HTTP(valueData.tokens.find("VALUE_2") != valueData.tokens.end(), Status::CODE_400, "No header 'VALUE_2' in value set"); + OATPP_ASSERT_HTTP(valueData.tokens.find("VALUE_3") != valueData.tokens.end(), Status::CODE_400, "No header 'VALUE_3' in value set"); return createResponse(Status::CODE_200, ""); } + ENDPOINT("POST", "test/multipart", multipartTest, REQUEST(std::shared_ptr, request)) { + + oatpp::web::mime::multipart::Multipart multipart(request->getHeaders()); + oatpp::web::mime::multipart::InMemoryReader multipartReader(&multipart); + request->transferBody(&multipartReader); + + for(auto& part : multipart.getAllParts()) { + OATPP_LOGD("multipart", "name='%s', value='%s'", part->getName()->getData(), part->getInMemoryData()->getData()); + } + + return createResponse(Status::CODE_200, ""); + + } + #include OATPP_CODEGEN_END(ApiController) }; diff --git a/test/oatpp/web/mime/multipart/StatefulParserTest.cpp b/test/oatpp/web/mime/multipart/StatefulParserTest.cpp index d0e0cedd..7c8e214d 100644 --- a/test/oatpp/web/mime/multipart/StatefulParserTest.cpp +++ b/test/oatpp/web/mime/multipart/StatefulParserTest.cpp @@ -24,8 +24,7 @@ #include "StatefulParserTest.hpp" -#include "oatpp/web/mime/multipart/Part.hpp" -#include "oatpp/web/mime/multipart/StatefulParser.hpp" +#include "oatpp/web/mime/multipart/InMemoryReader.hpp" #include "oatpp/core/data/stream/BufferInputStream.hpp" @@ -54,38 +53,10 @@ namespace { "--12345--\r\n" ; - class Listener : public oatpp::web::mime::multipart::StatefulParser::Listener { - private: - oatpp::data::stream::ChunkedBuffer m_buffer; - public: - - std::unordered_map> parts; - - void onPartHeaders(const oatpp::String& name, const Headers& partHeaders) override { - auto part = std::make_shared(partHeaders); - parts.insert({name, part}); - } - - void onPartData(const oatpp::String& name, p_char8 data, oatpp::data::v_io_size size) override { - - if(size > 0) { - m_buffer.write(data, size); - } else { - auto data = m_buffer.toString(); - m_buffer.clear(); - auto& part = parts[name]; - OATPP_ASSERT(part); - auto stream = std::make_shared(data.getPtr(), data->getData(), data->getSize()); - part->setDataInfo(stream, data, data->getSize()); - } - - } - - }; void parseStepByStep(const oatpp::String& text, const oatpp::String& boundary, - const std::shared_ptr& listener, + const std::shared_ptr& listener, v_int32 step) { @@ -128,20 +99,20 @@ void StatefulParserTest::onRun() { for(v_int32 i = 1; i < text->getSize(); i++) { - auto listener = std::make_shared(); + oatpp::web::mime::multipart::Multipart multipart("12345"); + + auto listener = std::make_shared(&multipart); parseStepByStep(text, "12345", listener, i); - auto& parts = listener->parts; - - if(parts.size() != 3) { + if(multipart.count() != 3) { OATPP_LOGD(TAG, "TEST_DATA_1 itearation %d", i); } - OATPP_ASSERT(parts.size() == 3); + OATPP_ASSERT(multipart.count() == 3); - auto part1 = parts["part1"]; - auto part2 = parts["part2"]; - auto part3 = parts["part3"]; + auto part1 = multipart.getNamedPart("part1"); + auto part2 = multipart.getNamedPart("part2"); + auto part3 = multipart.getNamedPart("part3"); OATPP_ASSERT(part1); OATPP_ASSERT(part2);