mirror of
https://gitee.com/an-tao/drogon.git
synced 2024-12-02 03:38:03 +08:00
Fix a bug when parsing multipart/form-data (#1358)
This commit is contained in:
parent
f582a16adb
commit
c40bb2bc1f
@ -33,34 +33,34 @@ class HttpFileImpl;
|
||||
class DROGON_EXPORT HttpFile
|
||||
{
|
||||
public:
|
||||
HttpFile(std::shared_ptr<HttpFileImpl> &&implPtr);
|
||||
explicit HttpFile(std::shared_ptr<HttpFileImpl> &&implPtr) noexcept;
|
||||
/// Return the file name;
|
||||
const std::string &getFileName() const;
|
||||
const std::string &getFileName() const noexcept;
|
||||
|
||||
/// Return the file extension;
|
||||
/// Note: After the HttpFile object is destroyed, do not use this
|
||||
/// string_view object.
|
||||
string_view getFileExtension() const;
|
||||
string_view getFileExtension() const noexcept;
|
||||
|
||||
/// Return the name of the item in multiple parts.
|
||||
const std::string &getItemName() const;
|
||||
const std::string &getItemName() const noexcept;
|
||||
|
||||
/// Return the type of file.
|
||||
FileType getFileType() const;
|
||||
FileType getFileType() const noexcept;
|
||||
|
||||
/// Set the file name, usually called by the MultiPartParser parser.
|
||||
void setFileName(const std::string &fileName);
|
||||
void setFileName(const std::string &fileName) noexcept;
|
||||
|
||||
/// Set the contents of the file, usually called by the MultiPartParser
|
||||
/// parser.
|
||||
void setFile(const char *data, size_t length);
|
||||
void setFile(const char *data, size_t length) noexcept;
|
||||
|
||||
/// Save the file to the file system.
|
||||
/**
|
||||
* The folder saving the file is app().getUploadPath().
|
||||
* The full path is app().getUploadPath()+"/"+this->getFileName()
|
||||
*/
|
||||
int save() const;
|
||||
int save() const noexcept;
|
||||
|
||||
/// Save the file to @param path
|
||||
/**
|
||||
@ -69,7 +69,7 @@ class DROGON_EXPORT HttpFile
|
||||
* otherwise the file is saved as
|
||||
* app().getUploadPath()+"/"+path+"/"+this->getFileName()
|
||||
*/
|
||||
int save(const std::string &path) const;
|
||||
int save(const std::string &path) const noexcept;
|
||||
|
||||
/// Save the file to file system with a new name
|
||||
/**
|
||||
@ -77,14 +77,14 @@ class DROGON_EXPORT HttpFile
|
||||
* the full path is app().getUploadPath()+"/"+filename, otherwise the file
|
||||
* is saved as the filename
|
||||
*/
|
||||
int saveAs(const std::string &fileName) const;
|
||||
int saveAs(const std::string &fileName) const noexcept;
|
||||
|
||||
/**
|
||||
* @brief return the content of the file.
|
||||
*
|
||||
* @return string_view
|
||||
*/
|
||||
string_view fileContent() const
|
||||
string_view fileContent() const noexcept
|
||||
{
|
||||
return string_view{fileData(), fileLength()};
|
||||
}
|
||||
@ -92,6 +92,8 @@ class DROGON_EXPORT HttpFile
|
||||
/// Return the file length.
|
||||
size_t fileLength() const noexcept;
|
||||
|
||||
/// Return the content-type of the file.
|
||||
drogon::ContentType getContentType() const noexcept;
|
||||
/**
|
||||
* @brief return the pointer of the file data.
|
||||
*
|
||||
@ -105,7 +107,10 @@ class DROGON_EXPORT HttpFile
|
||||
const char *fileData() const noexcept;
|
||||
|
||||
/// Return the md5 string of the file
|
||||
std::string getMd5() const;
|
||||
std::string getMd5() const noexcept;
|
||||
|
||||
/// Return the content transfer encoding of the file.
|
||||
const std::string &getContentTransferEncoding() const noexcept;
|
||||
|
||||
private:
|
||||
std::shared_ptr<HttpFileImpl> implPtr_;
|
||||
|
@ -22,12 +22,12 @@
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
int HttpFileImpl::save() const
|
||||
int HttpFileImpl::save() const noexcept
|
||||
{
|
||||
return save(HttpAppFrameworkImpl::instance().getUploadPath());
|
||||
}
|
||||
|
||||
int HttpFileImpl::save(const std::string &path) const
|
||||
int HttpFileImpl::save(const std::string &path) const noexcept
|
||||
{
|
||||
assert(!path.empty());
|
||||
if (fileName_.empty())
|
||||
@ -79,7 +79,7 @@ int HttpFileImpl::save(const std::string &path) const
|
||||
|
||||
return saveTo(fsSaveToPath);
|
||||
}
|
||||
int HttpFileImpl::saveAs(const std::string &fileName) const
|
||||
int HttpFileImpl::saveAs(const std::string &fileName) const noexcept
|
||||
{
|
||||
assert(!fileName.empty());
|
||||
filesystem::path fsFileName(utils::toNativePath(fileName));
|
||||
@ -105,7 +105,7 @@ int HttpFileImpl::saveAs(const std::string &fileName) const
|
||||
}
|
||||
return saveTo(fsFileName);
|
||||
}
|
||||
int HttpFileImpl::saveTo(const filesystem::path &pathAndFileName) const
|
||||
int HttpFileImpl::saveTo(const filesystem::path &pathAndFileName) const noexcept
|
||||
{
|
||||
LOG_TRACE << "save uploaded file:" << pathAndFileName;
|
||||
auto wPath = utils::toNativePath(pathAndFileName.native());
|
||||
@ -123,47 +123,47 @@ int HttpFileImpl::saveTo(const filesystem::path &pathAndFileName) const
|
||||
}
|
||||
}
|
||||
|
||||
std::string HttpFileImpl::getMd5() const
|
||||
std::string HttpFileImpl::getMd5() const noexcept
|
||||
{
|
||||
return utils::getMd5(fileContent_.data(), fileContent_.size());
|
||||
}
|
||||
|
||||
const std::string &HttpFile::getFileName() const
|
||||
const std::string &HttpFile::getFileName() const noexcept
|
||||
{
|
||||
return implPtr_->getFileName();
|
||||
}
|
||||
|
||||
void HttpFile::setFileName(const std::string &fileName)
|
||||
void HttpFile::setFileName(const std::string &fileName) noexcept
|
||||
{
|
||||
implPtr_->setFileName(fileName);
|
||||
}
|
||||
|
||||
string_view HttpFile::getFileExtension() const
|
||||
string_view HttpFile::getFileExtension() const noexcept
|
||||
{
|
||||
return implPtr_->getFileExtension();
|
||||
}
|
||||
|
||||
FileType HttpFile::getFileType() const
|
||||
FileType HttpFile::getFileType() const noexcept
|
||||
{
|
||||
return implPtr_->getFileType();
|
||||
}
|
||||
|
||||
void HttpFile::setFile(const char *data, size_t length)
|
||||
void HttpFile::setFile(const char *data, size_t length) noexcept
|
||||
{
|
||||
implPtr_->setFile(data, length);
|
||||
}
|
||||
|
||||
int HttpFile::save() const
|
||||
int HttpFile::save() const noexcept
|
||||
{
|
||||
return implPtr_->save();
|
||||
}
|
||||
|
||||
int HttpFile::save(const std::string &path) const
|
||||
int HttpFile::save(const std::string &path) const noexcept
|
||||
{
|
||||
return implPtr_->save(path);
|
||||
}
|
||||
|
||||
int HttpFile::saveAs(const std::string &fileName) const
|
||||
int HttpFile::saveAs(const std::string &fileName) const noexcept
|
||||
{
|
||||
return implPtr_->saveAs(fileName);
|
||||
}
|
||||
@ -173,22 +173,30 @@ size_t HttpFile::fileLength() const noexcept
|
||||
return implPtr_->fileLength();
|
||||
}
|
||||
|
||||
drogon::ContentType HttpFile::getContentType() const noexcept
|
||||
{
|
||||
return implPtr_->getContentType();
|
||||
}
|
||||
|
||||
const char *HttpFile::fileData() const noexcept
|
||||
{
|
||||
return implPtr_->fileData();
|
||||
}
|
||||
|
||||
std::string HttpFile::getMd5() const
|
||||
std::string HttpFile::getMd5() const noexcept
|
||||
{
|
||||
return implPtr_->getMd5();
|
||||
}
|
||||
|
||||
HttpFile::HttpFile(std::shared_ptr<HttpFileImpl> &&implPtr)
|
||||
const std::string &HttpFile::getContentTransferEncoding() const noexcept
|
||||
{
|
||||
return implPtr_->getContentTransferEncoding();
|
||||
}
|
||||
HttpFile::HttpFile(std::shared_ptr<HttpFileImpl> &&implPtr) noexcept
|
||||
: implPtr_(std::move(implPtr))
|
||||
{
|
||||
}
|
||||
|
||||
const std::string &HttpFile::getItemName() const
|
||||
const std::string &HttpFile::getItemName() const noexcept
|
||||
{
|
||||
return implPtr_->getItemName();
|
||||
}
|
||||
|
@ -29,26 +29,29 @@ class HttpFileImpl
|
||||
{
|
||||
public:
|
||||
/// Return the file name;
|
||||
const std::string &getFileName() const
|
||||
const std::string &getFileName() const noexcept
|
||||
{
|
||||
return fileName_;
|
||||
}
|
||||
|
||||
/// Set the file name, usually called by the MultiPartParser parser.
|
||||
void setFileName(const std::string &fileName)
|
||||
void setFileName(const std::string &fileName) noexcept
|
||||
{
|
||||
fileName_ = fileName;
|
||||
}
|
||||
|
||||
void setFileName(std::string &&fileName) noexcept
|
||||
{
|
||||
fileName_ = std::move(fileName);
|
||||
}
|
||||
/// Return the file extension;
|
||||
string_view getFileExtension() const
|
||||
string_view getFileExtension() const noexcept
|
||||
{
|
||||
return drogon::getFileExtension(fileName_);
|
||||
}
|
||||
|
||||
/// Set the contents of the file, usually called by the MultiPartParser
|
||||
/// parser.
|
||||
void setFile(const char *data, size_t length)
|
||||
void setFile(const char *data, size_t length) noexcept
|
||||
{
|
||||
fileContent_ = string_view{data, length};
|
||||
}
|
||||
@ -58,7 +61,7 @@ class HttpFileImpl
|
||||
* The folder saving the file is app().getUploadPath().
|
||||
* The full path is app().getUploadPath()+"/"+this->getFileName()
|
||||
*/
|
||||
int save() const;
|
||||
int save() const noexcept;
|
||||
|
||||
/// Save the file to @param path
|
||||
/**
|
||||
@ -67,7 +70,7 @@ class HttpFileImpl
|
||||
* otherwise the file is saved as
|
||||
* app().getUploadPath()+"/"+path+"/"+this->getFileName()
|
||||
*/
|
||||
int save(const std::string &path) const;
|
||||
int save(const std::string &path) const noexcept;
|
||||
|
||||
/// Save the file to file system with a new name
|
||||
/**
|
||||
@ -75,7 +78,7 @@ class HttpFileImpl
|
||||
* the full path is app().getUploadPath()+"/"+filename, otherwise the file
|
||||
* is saved as the filename
|
||||
*/
|
||||
int saveAs(const std::string &fileName) const;
|
||||
int saveAs(const std::string &fileName) const noexcept;
|
||||
|
||||
/// Return the file length.
|
||||
size_t fileLength() const noexcept
|
||||
@ -94,35 +97,62 @@ class HttpFileImpl
|
||||
}
|
||||
|
||||
/// Return the name of the item in multiple parts.
|
||||
const std::string &getItemName() const
|
||||
const std::string &getItemName() const noexcept
|
||||
{
|
||||
return itemName_;
|
||||
}
|
||||
|
||||
void setItemName(const std::string &itemName)
|
||||
void setItemName(const std::string &itemName) noexcept
|
||||
{
|
||||
itemName_ = itemName;
|
||||
}
|
||||
|
||||
void setItemName(std::string &&itemName) noexcept
|
||||
{
|
||||
itemName_ = std::move(itemName);
|
||||
}
|
||||
/// Return the type of file.
|
||||
FileType getFileType() const
|
||||
FileType getFileType() const noexcept
|
||||
{
|
||||
return parseFileType(getFileExtension());
|
||||
}
|
||||
|
||||
/// Return the md5 string of the file
|
||||
std::string getMd5() const;
|
||||
std::string getMd5() const noexcept;
|
||||
// int saveTo(const std::string &pathAndFileName) const;
|
||||
int saveTo(const filesystem::path &pathAndFileName) const;
|
||||
void setRequest(const HttpRequestPtr &req)
|
||||
int saveTo(const filesystem::path &pathAndFileName) const noexcept;
|
||||
void setRequest(const HttpRequestPtr &req) noexcept
|
||||
{
|
||||
requestPtr_ = req;
|
||||
}
|
||||
drogon::ContentType getContentType() const noexcept
|
||||
{
|
||||
return contentType_;
|
||||
}
|
||||
void setContentType(drogon::ContentType contentType) noexcept
|
||||
{
|
||||
contentType_ = contentType;
|
||||
}
|
||||
void setContentTransferEncoding(
|
||||
const std::string &contentTransferEncoding) noexcept
|
||||
{
|
||||
transferEncoding_ = contentTransferEncoding;
|
||||
}
|
||||
void setContentTransferEncoding(
|
||||
std::string &&contentTransferEncoding) noexcept
|
||||
{
|
||||
transferEncoding_ = std::move(contentTransferEncoding);
|
||||
}
|
||||
const std::string &getContentTransferEncoding() const noexcept
|
||||
{
|
||||
return transferEncoding_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string fileName_;
|
||||
std::string itemName_;
|
||||
std::string transferEncoding_;
|
||||
string_view fileContent_;
|
||||
HttpRequestPtr requestPtr_;
|
||||
drogon::ContentType contentType_{drogon::CT_NONE};
|
||||
};
|
||||
} // namespace drogon
|
||||
|
@ -75,69 +75,132 @@ int MultiPartParser::parse(const HttpRequestPtr &req)
|
||||
contentType.data() + (pos + 9),
|
||||
contentType.size() - (pos + 9));
|
||||
}
|
||||
|
||||
static std::pair<string_view, string_view> parseLine(const char *begin,
|
||||
const char *end)
|
||||
{
|
||||
auto p = begin;
|
||||
while (p != end)
|
||||
{
|
||||
if (*p == ':')
|
||||
{
|
||||
if (p + 1 != end && *(p + 1) == ' ')
|
||||
{
|
||||
return std::make_pair(string_view(begin, p - begin),
|
||||
string_view(p + 2, end - p - 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::make_pair(string_view(begin, p - begin),
|
||||
string_view(p + 1, end - p - 1));
|
||||
}
|
||||
}
|
||||
++p;
|
||||
}
|
||||
return std::make_pair(string_view(), string_view());
|
||||
}
|
||||
int MultiPartParser::parseEntity(const char *begin, const char *end)
|
||||
{
|
||||
static const char entityName[] = "name=";
|
||||
static const char semiColon[] = ";";
|
||||
static const char fileName[] = "filename=";
|
||||
static const char CRLF[] = "\r\n\r\n";
|
||||
|
||||
auto pos = std::search(begin, end, entityName, entityName + 5);
|
||||
if (pos == end)
|
||||
auto headEnd = std::search(begin, end, CRLF, CRLF + 4);
|
||||
if (headEnd == end)
|
||||
{
|
||||
return -1;
|
||||
pos += 5;
|
||||
auto pos1 = std::search(pos, end, semiColon, semiColon + 1);
|
||||
if (pos1 == end)
|
||||
{
|
||||
pos1 = std::search(pos, end, CRLF, CRLF + 2);
|
||||
if (pos1 == end)
|
||||
return -1;
|
||||
}
|
||||
if (*pos == '"')
|
||||
pos++;
|
||||
if (*(pos1 - 1) == '"')
|
||||
pos1--;
|
||||
std::string name(pos, pos1);
|
||||
pos = std::search(pos1, end, fileName, fileName + 9);
|
||||
if (pos == end)
|
||||
headEnd += 2;
|
||||
auto pos = begin;
|
||||
std::shared_ptr<HttpFileImpl> filePtr = std::make_shared<HttpFileImpl>();
|
||||
while (pos != headEnd)
|
||||
{
|
||||
pos1 = std::search(pos1, end, CRLF, CRLF + 4);
|
||||
if (pos1 == end)
|
||||
auto lineEnd = std::search(pos, headEnd, CRLF, CRLF + 2);
|
||||
auto keyAndValue = parseLine(pos, lineEnd);
|
||||
if (keyAndValue.first.empty() || keyAndValue.second.empty())
|
||||
{
|
||||
return -1;
|
||||
parameters_[name] = std::string(pos1 + 4, end);
|
||||
}
|
||||
pos = lineEnd + 2;
|
||||
std::string key{keyAndValue.first.data(), keyAndValue.first.size()};
|
||||
std::transform(key.begin(),
|
||||
key.end(),
|
||||
key.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
if (key == "content-disposition")
|
||||
{
|
||||
auto value = keyAndValue.second;
|
||||
auto valueEnd = value.data() + value.length();
|
||||
auto namePos =
|
||||
std::search(value.data(), valueEnd, entityName, entityName + 5);
|
||||
if (namePos == valueEnd)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
namePos += 5;
|
||||
const char *nameEnd;
|
||||
if (*namePos == '"')
|
||||
{
|
||||
++namePos;
|
||||
nameEnd = std::find(namePos, valueEnd, '"');
|
||||
}
|
||||
else
|
||||
{
|
||||
nameEnd = std::find(namePos, valueEnd, ';');
|
||||
}
|
||||
std::string name(namePos, nameEnd);
|
||||
auto fileNamePos =
|
||||
std::search(nameEnd, valueEnd, fileName, fileName + 9);
|
||||
if (fileNamePos == valueEnd)
|
||||
{
|
||||
parameters_.emplace(name, std::string(headEnd + 2, end));
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileNamePos += 9;
|
||||
const char *fileNameEnd;
|
||||
if (*fileNamePos == '"')
|
||||
{
|
||||
++fileNamePos;
|
||||
fileNameEnd = std::find(fileNamePos, valueEnd, '"');
|
||||
}
|
||||
else
|
||||
{
|
||||
fileNameEnd = std::find(fileNamePos, valueEnd, ';');
|
||||
}
|
||||
std::string fName{fileNamePos, fileNameEnd};
|
||||
filePtr->setRequest(requestPtr_);
|
||||
filePtr->setItemName(std::move(name));
|
||||
filePtr->setFileName(std::move(fName));
|
||||
filePtr->setFile(headEnd + 2,
|
||||
static_cast<size_t>(end - headEnd - 2));
|
||||
}
|
||||
}
|
||||
else if (key == "content-type")
|
||||
{
|
||||
auto value = keyAndValue.second;
|
||||
auto semiColonPos =
|
||||
std::find(value.data(), value.data() + value.length(), ';');
|
||||
string_view contentType(value.data(), semiColonPos - value.data());
|
||||
filePtr->setContentType(parseContentType(contentType));
|
||||
}
|
||||
else if (key == "content-transfer-encoding")
|
||||
{
|
||||
auto value = keyAndValue.second;
|
||||
auto semiColonPos =
|
||||
std::find(value.data(), value.data() + value.length(), ';');
|
||||
|
||||
filePtr->setContentTransferEncoding(
|
||||
std::string{value.data(), semiColonPos});
|
||||
}
|
||||
}
|
||||
if (!filePtr->getFileName().empty())
|
||||
{
|
||||
files_.emplace_back(std::move(filePtr));
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos += 9;
|
||||
pos1 = std::search(pos, end, semiColon, semiColon + 1);
|
||||
if (pos1 == end)
|
||||
{
|
||||
pos1 = std::search(pos, end, CRLF, CRLF + 2);
|
||||
if (pos1 == end)
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto pos2 = std::search(pos, pos1, CRLF, CRLF + 2);
|
||||
if (pos2 != end)
|
||||
pos1 = pos2;
|
||||
}
|
||||
if (*pos == '"')
|
||||
pos++;
|
||||
if (*(pos1 - 1) == '"')
|
||||
pos1--;
|
||||
auto filePtr = std::make_shared<HttpFileImpl>();
|
||||
filePtr->setRequest(requestPtr_);
|
||||
filePtr->setItemName(name);
|
||||
filePtr->setFileName(std::string(pos, pos1));
|
||||
pos1 = std::search(pos1, end, CRLF, CRLF + 4);
|
||||
if (pos1 == end)
|
||||
return -1;
|
||||
filePtr->setFile(pos1 + 4, static_cast<size_t>(end - pos1 - 4));
|
||||
files_.emplace_back(std::move(filePtr));
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,7 +216,7 @@ int MultiPartParser::parse(const HttpRequestPtr &req,
|
||||
pos1 = 0;
|
||||
auto content = static_cast<HttpRequestImpl *>(req.get())->bodyView();
|
||||
pos2 = content.find(boundary);
|
||||
while (1)
|
||||
while (true)
|
||||
{
|
||||
pos1 = pos2;
|
||||
if (pos1 == string_view::npos)
|
||||
@ -164,13 +227,17 @@ int MultiPartParser::parse(const HttpRequestPtr &req,
|
||||
pos2 = content.find(boundary, pos1);
|
||||
if (pos2 == string_view::npos)
|
||||
break;
|
||||
// std::cout<<"pos1="<<pos1<<" pos2="<<pos2<<std::endl;
|
||||
bool flag = false;
|
||||
if (content[pos2 - 4] == '\r' && content[pos2 - 3] == '\n' &&
|
||||
content[pos2 - 2] == '-' && content[pos2 - 1] == '-')
|
||||
{
|
||||
pos2 -= 4;
|
||||
flag = true;
|
||||
}
|
||||
if (parseEntity(content.data() + pos1, content.data() + pos2) != 0)
|
||||
return -1;
|
||||
// pos2+=boundary.length();
|
||||
if (flag)
|
||||
pos2 += 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -21,7 +21,8 @@ set(UNITTEST_SOURCES
|
||||
unittests/MainLoopTest.cc
|
||||
unittests/CacheMapTest.cc
|
||||
unittests/StringOpsTest.cc
|
||||
unittests/ControllerCreationTest.cc)
|
||||
unittests/ControllerCreationTest.cc
|
||||
unittests/MultiPartParserTest.cc)
|
||||
|
||||
if(DROGON_CXX_STANDARD GREATER_EQUAL 20 AND HAS_COROUTINE)
|
||||
set(UNITTEST_SOURCES ${UNITTEST_SOURCES} unittests/CoroutineTest.cc)
|
||||
|
62
lib/tests/unittests/MultiPartParserTest.cc
Normal file
62
lib/tests/unittests/MultiPartParserTest.cc
Normal file
@ -0,0 +1,62 @@
|
||||
#include <drogon/MultiPart.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/HttpRequest.h>
|
||||
|
||||
DROGON_TEST(MultiPartParser)
|
||||
{
|
||||
drogon::MultiPartParser parser1;
|
||||
auto req = drogon::HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Post);
|
||||
req->addHeader("content-type", "multipart/form-data; boundary=\"12345\"");
|
||||
req->setBody(
|
||||
"--12345\r\n"
|
||||
"Content-Disposition: form-data; name=\"somekey\"\r\n"
|
||||
"\r\n"
|
||||
"Hello; World\r\n"
|
||||
"--12345--");
|
||||
CHECK(0 == parser1.parse(req));
|
||||
CHECK(parser1.getParameters().size() == 1);
|
||||
CHECK(parser1.getParameters().at("somekey") == "Hello; World");
|
||||
req->setBody(
|
||||
"--12345\r\n"
|
||||
"Content-Disposition: form-data; name=\"somekey\"; "
|
||||
"filename=\"test\"\r\n"
|
||||
"\r\n"
|
||||
"Hello; World\r\n"
|
||||
"--12345--");
|
||||
drogon::MultiPartParser parser2;
|
||||
CHECK(0 == parser2.parse(req));
|
||||
auto filesMap = parser2.getFilesMap();
|
||||
CHECK(filesMap.size() == 1);
|
||||
CHECK(filesMap.at("somekey").getFileName() == "test");
|
||||
CHECK(filesMap.at("somekey").fileContent() == "Hello; World");
|
||||
req->setBody(
|
||||
"--12345\r\n"
|
||||
"Content-Disposition: form-data; name=\"name of pdf\"; "
|
||||
"filename=\"pdf-file.pdf\"\r\n"
|
||||
"Content-Type: application/octet-stream\r\n"
|
||||
"content-transfer-encoding: quoted-printable\r\n"
|
||||
"\r\n"
|
||||
"bytes of pdf file\r\n"
|
||||
"--12345--");
|
||||
drogon::MultiPartParser parser3;
|
||||
CHECK(0 == parser3.parse(req));
|
||||
filesMap = parser3.getFilesMap();
|
||||
CHECK(filesMap.size() == 1);
|
||||
CHECK(filesMap.at("name of pdf").getFileName() == "pdf-file.pdf");
|
||||
CHECK(filesMap.at("name of pdf").fileContent() == "bytes of pdf file");
|
||||
CHECK(filesMap.at("name of pdf").getContentType() ==
|
||||
drogon::CT_APPLICATION_OCTET_STREAM);
|
||||
CHECK(filesMap.at("name of pdf").getContentTransferEncoding() ==
|
||||
"quoted-printable");
|
||||
req->setBody(
|
||||
"--12345\r\n"
|
||||
"Content-Disposition: form-data; name=\"some;key\"\r\n"
|
||||
"\r\n"
|
||||
"Hello; World\r\n"
|
||||
"--12345--");
|
||||
drogon::MultiPartParser parser4;
|
||||
CHECK(0 == parser4.parse(req));
|
||||
CHECK(parser4.getParameters().size() == 1);
|
||||
CHECK(parser4.getParameters().at("some;key") == "Hello; World");
|
||||
}
|
Loading…
Reference in New Issue
Block a user