mirror of
https://github.com/oatpp/oatpp.git
synced 2025-02-11 17:20:52 +08:00
Introduce Multipart InMemoryReader
This commit is contained in:
parent
a032358614
commit
cc4f27d41e
@ -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
|
||||
|
@ -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 "'"); \
|
||||
}
|
||||
|
@ -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<async::Action>(nextAction));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DefaultWriteCallback
|
||||
|
@ -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. <br>
|
||||
* 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! <br>
|
||||
* Write Callback. Data should be fully utilized on call to this method - no partial writes for this method. <br>
|
||||
* 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. <br>
|
||||
* Uses &l:writeExactSizeData (); method underhood.
|
||||
|
85
src/oatpp/web/mime/multipart/InMemoryReader.cpp
Normal file
85
src/oatpp/web/mime/multipart/InMemoryReader.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* Project _____ __ ____ _ _
|
||||
* ( _ ) /__\ (_ _)_| |_ _| |_
|
||||
* )(_)( /(__)\ )( (_ _)(_ _)
|
||||
* (_____)(__)(__)(__) |_| |_|
|
||||
*
|
||||
*
|
||||
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
|
||||
*
|
||||
* 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<Part>(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<data::stream::BufferInputStream>(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<InMemoryParser>(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<InMemoryParser>(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<async::Action>(nextAction);
|
||||
}
|
||||
|
||||
}}}}
|
98
src/oatpp/web/mime/multipart/InMemoryReader.hpp
Normal file
98
src/oatpp/web/mime/multipart/InMemoryReader.hpp
Normal file
@ -0,0 +1,98 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* Project _____ __ ____ _ _
|
||||
* ( _ ) /__\ (_ _)_| |_ _| |_
|
||||
* )(_)( /(__)\ )( (_ _)(_ _)
|
||||
* (_____)(__)(__)(__) |_| |_|
|
||||
*
|
||||
*
|
||||
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
|
||||
*
|
||||
* 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. <br>
|
||||
* Extends - &id:oatpp::web::mime::multipart::StatefulParser::Listener;.
|
||||
*/
|
||||
class InMemoryParser : public StatefulParser::Listener {
|
||||
private:
|
||||
Multipart* m_multipart;
|
||||
std::shared_ptr<Part> 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
|
94
src/oatpp/web/mime/multipart/Multipart.cpp
Normal file
94
src/oatpp/web/mime/multipart/Multipart.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* Project _____ __ ____ _ _
|
||||
* ( _ ) /__\ (_ _)_| |_ _| |_
|
||||
* )(_)( /(__)\ )( (_ _)(_ _)
|
||||
* (_____)(__)(__)(__) |_| |_|
|
||||
*
|
||||
*
|
||||
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
|
||||
*
|
||||
* 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>& 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<Part> 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<std::shared_ptr<Part>>& Multipart::getAllParts() {
|
||||
return m_parts;
|
||||
}
|
||||
|
||||
v_int32 Multipart::count() {
|
||||
return m_parts.size();
|
||||
}
|
||||
|
||||
}}}}
|
104
src/oatpp/web/mime/multipart/Multipart.hpp
Normal file
104
src/oatpp/web/mime/multipart/Multipart.hpp
Normal file
@ -0,0 +1,104 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* Project _____ __ ____ _ _
|
||||
* ( _ ) /__\ (_ _)_| |_ _| |_
|
||||
* )(_)( /(__)\ )( (_ _)(_ _)
|
||||
* (_____)(__)(__)(__) |_| |_|
|
||||
*
|
||||
*
|
||||
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
|
||||
*
|
||||
* 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 <unordered_map>
|
||||
#include <list>
|
||||
|
||||
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<oatpp::data::share::StringKeyLabelCI_FAST, oatpp::data::share::StringKeyLabel> Headers;
|
||||
|
||||
/**
|
||||
* Structure that holds parts of Multipart.
|
||||
*/
|
||||
class Multipart {
|
||||
private:
|
||||
oatpp::String m_boundary;
|
||||
std::unordered_map<oatpp::String, std::shared_ptr<Part>> m_namedParts;
|
||||
std::list<std::shared_ptr<Part>> 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>& part);
|
||||
|
||||
/**
|
||||
* Get part by name <br>
|
||||
* Applicable to named parts only.
|
||||
* @param name - &id:oatpp::String;.
|
||||
* @return - &id:oatpp::web::mime::multipart::Part;.
|
||||
*/
|
||||
std::shared_ptr<Part> 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<std::shared_ptr<Part>>& getAllParts();
|
||||
|
||||
/**
|
||||
* Get parts count.
|
||||
* @return - parts count.
|
||||
*/
|
||||
v_int32 count();
|
||||
|
||||
};
|
||||
|
||||
}}}}
|
||||
|
||||
|
||||
#endif // oatpp_web_mime_multipart_Multipart_hpp
|
@ -24,46 +24,13 @@
|
||||
|
||||
#include "Part.hpp"
|
||||
|
||||
#include "oatpp/web/protocol/http/Http.hpp"
|
||||
#include "oatpp/core/parser/Caret.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
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<data::stream::InputStream> &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.");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<oatpp::data::share::StringKeyLabelCI_FAST, oatpp::data::share::StringKeyLabel> 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;
|
||||
|
@ -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>& 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<oatpp::data::share::StringKeyLabelCI_FAST, oatpp::data::share::StringKeyLabel> 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<oatpp::data::share::StringKeyLabelCI_FAST, oatpp::data::share::StringKeyLabel> 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.
|
||||
|
@ -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<oatpp::base::StrBuffer>& headersText,
|
||||
oatpp::parser::Caret& caret) {
|
||||
@ -351,20 +360,47 @@ void Parser::parseHeaders(Headers& headers,
|
||||
|
||||
}
|
||||
|
||||
std::unordered_set<oatpp::data::share::StringKeyLabelCI> 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<oatpp::data::share::StringKeyLabelCI> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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<data::share::StringKeyLabelCI> tokens;
|
||||
|
||||
/**
|
||||
* Title params.
|
||||
*/
|
||||
std::unordered_map<data::share::StringKeyLabelCI, data::share::StringKeyLabel> 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<oatpp::data::share::StringKeyLabelCI> parseHeaderValueSet(const oatpp::data::share::StringKeyLabel& headerValue, char separator);
|
||||
|
||||
static void parseHeaderValueData(HeaderValueData& data, const oatpp::data::share::StringKeyLabel& headerValue, v_char8 separator);
|
||||
|
||||
};
|
||||
|
||||
}}}}
|
||||
|
@ -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<IncomingRequest>, 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)
|
||||
|
||||
};
|
||||
|
@ -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<oatpp::String, std::shared_ptr<Part>> parts;
|
||||
|
||||
void onPartHeaders(const oatpp::String& name, const Headers& partHeaders) override {
|
||||
auto part = std::make_shared<Part>(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<oatpp::data::stream::BufferInputStream>(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>& listener,
|
||||
const std::shared_ptr<oatpp::web::mime::multipart::StatefulParser::Listener>& 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<Listener>();
|
||||
oatpp::web::mime::multipart::Multipart multipart("12345");
|
||||
|
||||
auto listener = std::make_shared<oatpp::web::mime::multipart::InMemoryParser>(&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);
|
||||
|
Loading…
Reference in New Issue
Block a user