Use string_view to parse multipart/form-data requests (#469)
This commit is contained in:
parent
be6f0966d5
commit
e7b7618c37
|
@ -129,6 +129,7 @@ set(DROGON_SOURCES
|
|||
lib/src/HttpAppFrameworkImpl.cc
|
||||
lib/src/HttpClientImpl.cc
|
||||
lib/src/HttpControllersRouter.cc
|
||||
lib/src/HttpFileImpl.cc
|
||||
lib/src/HttpFileUploadRequest.cc
|
||||
lib/src/HttpRequestImpl.cc
|
||||
lib/src/HttpRequestParser.cc
|
||||
|
|
|
@ -17,7 +17,7 @@ void Attachment::upload(const HttpRequestPtr &req,
|
|||
if (fileUpload.parse(req) == 0)
|
||||
{
|
||||
// LOG_DEBUG << "upload good!";
|
||||
auto files = fileUpload.getFiles();
|
||||
auto &files = fileUpload.getFiles();
|
||||
// LOG_DEBUG << "file num=" << files.size();
|
||||
for (auto const &file : files)
|
||||
{
|
||||
|
|
|
@ -14,36 +14,32 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <drogon/HttpRequest.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpFileImpl;
|
||||
/**
|
||||
* @brief This class represents a uploaded file by a HTTP request.
|
||||
*
|
||||
*/
|
||||
class HttpFile
|
||||
{
|
||||
public:
|
||||
HttpFile(std::shared_ptr<HttpFileImpl> &&implPtr);
|
||||
/// Return the file name;
|
||||
const std::string &getFileName() const
|
||||
{
|
||||
return fileName_;
|
||||
};
|
||||
const std::string &getFileName() const;
|
||||
|
||||
/// Set the file name
|
||||
void setFileName(const std::string &filename)
|
||||
{
|
||||
fileName_ = filename;
|
||||
};
|
||||
/// Set the file name, usually called by the MultiPartParser parser.
|
||||
void setFileName(const std::string &filename);
|
||||
|
||||
/// Set the contents of the file, usually called by the FileUpload parser.
|
||||
void setFile(const std::string &file)
|
||||
{
|
||||
fileContent_ = file;
|
||||
};
|
||||
void setFile(std::string &&file)
|
||||
{
|
||||
fileContent_ = std::move(file);
|
||||
}
|
||||
/// Set the contents of the file, usually called by the MultiPartParser
|
||||
/// parser.
|
||||
void setFile(const char *data, size_t length);
|
||||
|
||||
/// Save the file to the file system.
|
||||
/**
|
||||
|
@ -70,38 +66,16 @@ class HttpFile
|
|||
int saveAs(const std::string &filename) const;
|
||||
|
||||
/// Return the file length.
|
||||
int64_t fileLength() const noexcept
|
||||
{
|
||||
return fileContent_.length();
|
||||
};
|
||||
/// Return the file content.
|
||||
char *fileData() noexcept
|
||||
{
|
||||
#if __cplusplus >= 201703L || (defined _MSC_VER && _MSC_VER > 1900)
|
||||
return fileContent_.data();
|
||||
#else
|
||||
return (char *)(fileContent_.data());
|
||||
#endif
|
||||
}
|
||||
const char *fileData() const noexcept
|
||||
{
|
||||
return fileContent_.data();
|
||||
}
|
||||
std::string &fileContent() noexcept
|
||||
{
|
||||
return fileContent_;
|
||||
}
|
||||
const std::string &fileContent() const noexcept
|
||||
{
|
||||
return fileContent_;
|
||||
}
|
||||
size_t fileLength() const noexcept;
|
||||
|
||||
/// Return the file data.
|
||||
const char *fileData() const noexcept;
|
||||
|
||||
/// Return the md5 string of the file
|
||||
std::string getMd5() const;
|
||||
|
||||
protected:
|
||||
int saveTo(const std::string &pathAndFilename) const;
|
||||
std::string fileName_;
|
||||
std::string fileContent_;
|
||||
private:
|
||||
std::shared_ptr<HttpFileImpl> implPtr_;
|
||||
};
|
||||
|
||||
/// A parser class which help the user to get the files and the parameters in
|
||||
|
@ -113,7 +87,7 @@ class MultiPartParser
|
|||
~MultiPartParser(){};
|
||||
/// Get files, This method should be called after calling the parse()
|
||||
/// method.
|
||||
const std::vector<HttpFile> &getFiles();
|
||||
const std::vector<HttpFile> &getFiles() const;
|
||||
|
||||
/// Get parameters, This method should be called after calling the parse ()
|
||||
/// method.
|
||||
|
@ -125,8 +99,11 @@ class MultiPartParser
|
|||
protected:
|
||||
std::vector<HttpFile> files_;
|
||||
std::map<std::string, std::string> parameters_;
|
||||
int parse(const HttpRequestPtr &req, const std::string &boundary);
|
||||
int parse(const HttpRequestPtr &req,
|
||||
const char *boundaryData,
|
||||
size_t boundaryLen);
|
||||
int parseEntity(const char *begin, const char *end);
|
||||
HttpRequestPtr requestPtr_;
|
||||
};
|
||||
|
||||
/// In order to be compatible with old interfaces
|
||||
|
|
|
@ -98,7 +98,11 @@ std::string urlEncode(const std::string &);
|
|||
std::string urlEncodeComponent(const std::string &);
|
||||
|
||||
/// Get the MD5 digest of a string.
|
||||
std::string getMd5(const std::string &originalString);
|
||||
std::string getMd5(const char *data, const size_t dataLen);
|
||||
inline std::string getMd5(const std::string &originalString)
|
||||
{
|
||||
return getMd5(originalString.data(), originalString.length());
|
||||
}
|
||||
|
||||
/// Commpress or decompress data using gzip lib.
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
/**
|
||||
*
|
||||
* HttpFileImpl.cc
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpFileImpl.h"
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
#include <drogon/MultiPart.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
int HttpFileImpl::save(const std::string &path) const
|
||||
{
|
||||
assert(!path.empty());
|
||||
if (fileName_ == "")
|
||||
return -1;
|
||||
std::string filename;
|
||||
auto tmpPath = path;
|
||||
if (path[0] == '/' ||
|
||||
(path.length() >= 2 && path[0] == '.' && path[1] == '/') ||
|
||||
(path.length() >= 3 && path[0] == '.' && path[1] == '.' &&
|
||||
path[2] == '/') ||
|
||||
path == "." || path == "..")
|
||||
{
|
||||
// Absolute or relative path
|
||||
}
|
||||
else
|
||||
{
|
||||
auto &uploadPath = HttpAppFrameworkImpl::instance().getUploadPath();
|
||||
if (uploadPath[uploadPath.length() - 1] == '/')
|
||||
tmpPath = uploadPath + path;
|
||||
else
|
||||
tmpPath = uploadPath + "/" + path;
|
||||
}
|
||||
|
||||
if (utils::createPath(tmpPath) < 0)
|
||||
return -1;
|
||||
|
||||
if (tmpPath[tmpPath.length() - 1] != '/')
|
||||
{
|
||||
filename = tmpPath + "/";
|
||||
filename.append(fileName_.data(), fileName_.length());
|
||||
}
|
||||
else
|
||||
filename = tmpPath.append(fileName_.data(), fileName_.length());
|
||||
|
||||
return saveTo(filename);
|
||||
}
|
||||
int HttpFileImpl::save() const
|
||||
{
|
||||
return save(HttpAppFrameworkImpl::instance().getUploadPath());
|
||||
}
|
||||
int HttpFileImpl::saveAs(const std::string &filename) const
|
||||
{
|
||||
assert(!filename.empty());
|
||||
auto pathAndFileName = filename;
|
||||
if (filename[0] == '/' ||
|
||||
(filename.length() >= 2 && filename[0] == '.' && filename[1] == '/') ||
|
||||
(filename.length() >= 3 && filename[0] == '.' && filename[1] == '.' &&
|
||||
filename[2] == '/'))
|
||||
{
|
||||
// Absolute or relative path
|
||||
}
|
||||
else
|
||||
{
|
||||
auto &uploadPath = HttpAppFrameworkImpl::instance().getUploadPath();
|
||||
if (uploadPath[uploadPath.length() - 1] == '/')
|
||||
pathAndFileName = uploadPath + filename;
|
||||
else
|
||||
pathAndFileName = uploadPath + "/" + filename;
|
||||
}
|
||||
auto pathPos = pathAndFileName.rfind('/');
|
||||
if (pathPos != std::string::npos)
|
||||
{
|
||||
std::string path = pathAndFileName.substr(0, pathPos);
|
||||
if (utils::createPath(path) < 0)
|
||||
return -1;
|
||||
}
|
||||
return saveTo(pathAndFileName);
|
||||
}
|
||||
int HttpFileImpl::saveTo(const std::string &pathAndFilename) const
|
||||
{
|
||||
LOG_TRACE << "save uploaded file:" << pathAndFilename;
|
||||
std::ofstream file(pathAndFilename);
|
||||
if (file.is_open())
|
||||
{
|
||||
file.write(fileContent_.data(), fileContent_.size());
|
||||
file.close();
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "save failed!";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
std::string HttpFileImpl::getMd5() const
|
||||
{
|
||||
return utils::getMd5(fileContent_.data(), fileContent_.size());
|
||||
}
|
||||
|
||||
const std::string &HttpFile::getFileName() const
|
||||
{
|
||||
return implPtr_->getFileName();
|
||||
}
|
||||
|
||||
void HttpFile::setFileName(const std::string &filename)
|
||||
{
|
||||
implPtr_->setFileName(filename);
|
||||
}
|
||||
|
||||
void HttpFile::setFile(const char *data, size_t length)
|
||||
{
|
||||
implPtr_->setFile(data, length);
|
||||
}
|
||||
|
||||
int HttpFile::save() const
|
||||
{
|
||||
return implPtr_->save();
|
||||
}
|
||||
|
||||
int HttpFile::save(const std::string &path) const
|
||||
{
|
||||
return implPtr_->save(path);
|
||||
}
|
||||
|
||||
int HttpFile::saveAs(const std::string &filename) const
|
||||
{
|
||||
return implPtr_->saveAs(filename);
|
||||
}
|
||||
|
||||
size_t HttpFile::fileLength() const noexcept
|
||||
{
|
||||
return implPtr_->fileLength();
|
||||
}
|
||||
|
||||
const char *HttpFile::fileData() const noexcept
|
||||
{
|
||||
return implPtr_->fileData();
|
||||
}
|
||||
|
||||
std::string HttpFile::getMd5() const
|
||||
{
|
||||
return implPtr_->getMd5();
|
||||
}
|
||||
|
||||
HttpFile::HttpFile(std::shared_ptr<HttpFileImpl> &&implPtr)
|
||||
: implPtr_(std::move(implPtr))
|
||||
{
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
*
|
||||
* HttpFileImpl.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <drogon/utils/string_view.h>
|
||||
#include <drogon/HttpRequest.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
namespace drogon
|
||||
{
|
||||
class HttpFileImpl
|
||||
{
|
||||
public:
|
||||
/// Return the file name;
|
||||
const std::string &getFileName() const
|
||||
{
|
||||
return fileName_;
|
||||
};
|
||||
|
||||
/// Set the file name, usually called by the MultiPartParser parser.
|
||||
void setFileName(const std::string &filename)
|
||||
{
|
||||
fileName_ = filename;
|
||||
};
|
||||
|
||||
/// Set the contents of the file, usually called by the MultiPartParser
|
||||
/// parser.
|
||||
void setFile(const char *data, size_t length)
|
||||
{
|
||||
fileContent_ = string_view{data, length};
|
||||
};
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Save the file to @param path
|
||||
/**
|
||||
* @param path if the parameter is prefixed with "/", "./" or "../", or is
|
||||
* "." or "..", the full path is path+"/"+this->getFileName(),
|
||||
* otherwise the file is saved as
|
||||
* app().getUploadPath()+"/"+path+"/"+this->getFileName()
|
||||
*/
|
||||
int save(const std::string &path) const;
|
||||
|
||||
/// Save the file to file system with a new name
|
||||
/**
|
||||
* @param filename if the parameter isn't prefixed with "/", "./" or "../",
|
||||
* the full path is app().getUploadPath()+"/"+filename, otherwise the file
|
||||
* is saved as the filename
|
||||
*/
|
||||
int saveAs(const std::string &filename) const;
|
||||
|
||||
/// Return the file length.
|
||||
size_t fileLength() const noexcept
|
||||
{
|
||||
return fileContent_.length();
|
||||
};
|
||||
|
||||
const char *fileData() const noexcept
|
||||
{
|
||||
return fileContent_.data();
|
||||
}
|
||||
|
||||
const string_view &fileContent() const noexcept
|
||||
{
|
||||
return fileContent_;
|
||||
}
|
||||
|
||||
/// Return the md5 string of the file
|
||||
std::string getMd5() const;
|
||||
int saveTo(const std::string &pathAndFilename) const;
|
||||
void setRequest(const HttpRequestPtr &req)
|
||||
{
|
||||
requestPtr_ = req;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string fileName_;
|
||||
string_view fileContent_;
|
||||
HttpRequestPtr requestPtr_;
|
||||
};
|
||||
} // namespace drogon
|
|
@ -15,6 +15,7 @@
|
|||
#include "HttpRequestImpl.h"
|
||||
#include "HttpUtils.h"
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
#include "HttpFileImpl.h"
|
||||
#include <drogon/MultiPart.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/config.h>
|
||||
|
@ -29,7 +30,7 @@
|
|||
|
||||
using namespace drogon;
|
||||
|
||||
const std::vector<HttpFile> &MultiPartParser::getFiles()
|
||||
const std::vector<HttpFile> &MultiPartParser::getFiles() const
|
||||
{
|
||||
return files_;
|
||||
}
|
||||
|
@ -60,9 +61,9 @@ int MultiPartParser::parse(const HttpRequestPtr &req)
|
|||
pos = contentType.find("boundary=");
|
||||
if (pos == std::string::npos)
|
||||
return -1;
|
||||
std::string boundary = contentType.substr(pos + 9);
|
||||
|
||||
return parse(req, boundary);
|
||||
return parse(req,
|
||||
contentType.data() + (pos + 9),
|
||||
contentType.size() - (pos + 9));
|
||||
}
|
||||
|
||||
int MultiPartParser::parseEntity(const char *begin, const char *end)
|
||||
|
@ -95,20 +96,26 @@ int MultiPartParser::parseEntity(const char *begin, const char *end)
|
|||
auto pos1 = std::search(pos, end, quotationMark, quotationMark + 1);
|
||||
if (pos1 == end)
|
||||
return -1;
|
||||
HttpFile file;
|
||||
file.setFileName(std::string(pos, pos1));
|
||||
auto filePtr = std::make_shared<HttpFileImpl>();
|
||||
filePtr->setRequest(requestPtr_);
|
||||
filePtr->setFileName(std::string(pos, pos1));
|
||||
pos1 = std::search(pos1, end, CRLF, CRLF + 4);
|
||||
if (pos1 == end)
|
||||
return -1;
|
||||
file.setFile(std::string(pos1 + 4, end));
|
||||
files_.push_back(std::move(file));
|
||||
filePtr->setFile(pos1 + 4, static_cast<size_t>(end - pos1 - 4));
|
||||
files_.push_back(std::move(HttpFile(std::move(filePtr))));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int MultiPartParser::parse(const HttpRequestPtr &req,
|
||||
const std::string &boundary)
|
||||
const char *boundaryData,
|
||||
size_t boundaryLen)
|
||||
{
|
||||
string_view boundary{boundaryData, boundaryLen};
|
||||
if (boundary.size() > 2 && boundary[0] == '\"')
|
||||
boundary = boundary.substr(1, boundary.size() - 2);
|
||||
requestPtr_ = req;
|
||||
string_view::size_type pos1, pos2;
|
||||
pos1 = 0;
|
||||
auto content = static_cast<HttpRequestImpl *>(req.get())->bodyView();
|
||||
|
@ -134,92 +141,3 @@ int MultiPartParser::parse(const HttpRequestPtr &req,
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpFile::save(const std::string &path) const
|
||||
{
|
||||
assert(!path.empty());
|
||||
if (fileName_ == "")
|
||||
return -1;
|
||||
std::string filename;
|
||||
auto tmpPath = path;
|
||||
if (path[0] == '/' ||
|
||||
(path.length() >= 2 && path[0] == '.' && path[1] == '/') ||
|
||||
(path.length() >= 3 && path[0] == '.' && path[1] == '.' &&
|
||||
path[2] == '/') ||
|
||||
path == "." || path == "..")
|
||||
{
|
||||
// Absolute or relative path
|
||||
}
|
||||
else
|
||||
{
|
||||
auto &uploadPath = HttpAppFrameworkImpl::instance().getUploadPath();
|
||||
if (uploadPath[uploadPath.length() - 1] == '/')
|
||||
tmpPath = uploadPath + path;
|
||||
else
|
||||
tmpPath = uploadPath + "/" + path;
|
||||
}
|
||||
|
||||
if (utils::createPath(tmpPath) < 0)
|
||||
return -1;
|
||||
|
||||
if (tmpPath[tmpPath.length() - 1] != '/')
|
||||
{
|
||||
filename = tmpPath + "/" + fileName_;
|
||||
}
|
||||
else
|
||||
filename = tmpPath + fileName_;
|
||||
|
||||
return saveTo(filename);
|
||||
}
|
||||
int HttpFile::save() const
|
||||
{
|
||||
return save(HttpAppFrameworkImpl::instance().getUploadPath());
|
||||
}
|
||||
int HttpFile::saveAs(const std::string &filename) const
|
||||
{
|
||||
assert(!filename.empty());
|
||||
auto pathAndFileName = filename;
|
||||
if (filename[0] == '/' ||
|
||||
(filename.length() >= 2 && filename[0] == '.' && filename[1] == '/') ||
|
||||
(filename.length() >= 3 && filename[0] == '.' && filename[1] == '.' &&
|
||||
filename[2] == '/'))
|
||||
{
|
||||
// Absolute or relative path
|
||||
}
|
||||
else
|
||||
{
|
||||
auto &uploadPath = HttpAppFrameworkImpl::instance().getUploadPath();
|
||||
if (uploadPath[uploadPath.length() - 1] == '/')
|
||||
pathAndFileName = uploadPath + filename;
|
||||
else
|
||||
pathAndFileName = uploadPath + "/" + filename;
|
||||
}
|
||||
auto pathPos = pathAndFileName.rfind('/');
|
||||
if (pathPos != std::string::npos)
|
||||
{
|
||||
std::string path = pathAndFileName.substr(0, pathPos);
|
||||
if (utils::createPath(path) < 0)
|
||||
return -1;
|
||||
}
|
||||
return saveTo(pathAndFileName);
|
||||
}
|
||||
int HttpFile::saveTo(const std::string &pathAndFilename) const
|
||||
{
|
||||
LOG_TRACE << "save uploaded file:" << pathAndFilename;
|
||||
std::ofstream file(pathAndFilename);
|
||||
if (file.is_open())
|
||||
{
|
||||
file << fileContent_;
|
||||
file.close();
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "save failed!";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
std::string HttpFile::getMd5() const
|
||||
{
|
||||
return utils::getMd5(fileContent_);
|
||||
}
|
||||
|
|
|
@ -1144,17 +1144,17 @@ std::string brotliDecompress(const char *data, const size_t ndata)
|
|||
}
|
||||
#endif
|
||||
|
||||
std::string getMd5(const std::string &originalString)
|
||||
std::string getMd5(const char *data, const size_t dataLen)
|
||||
{
|
||||
#ifdef OpenSSL_FOUND
|
||||
MD5_CTX c;
|
||||
unsigned char md5[16] = {0};
|
||||
MD5_Init(&c);
|
||||
MD5_Update(&c, originalString.c_str(), originalString.size());
|
||||
MD5_Update(&c, data, dataLen);
|
||||
MD5_Final(md5, &c);
|
||||
return utils::binaryStringToHex(md5, 16);
|
||||
#else
|
||||
return Md5Encode::encode(originalString);
|
||||
return Md5Encode::encode(data, dataLen);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -320,7 +320,7 @@ std::string Md5Encode::getHexStr(uint32_t numStr)
|
|||
// function: Encode
|
||||
// @param srcInfo: the string to be encoded.
|
||||
// return : the string after encoding
|
||||
std::string Md5Encode::encode(const std::string &srcInfo)
|
||||
std::string Md5Encode::encode(const char *data, const size_t dataLen)
|
||||
{
|
||||
ParamDynamic param;
|
||||
param.ua_ = kA;
|
||||
|
@ -334,7 +334,7 @@ std::string Md5Encode::encode(const std::string &srcInfo)
|
|||
|
||||
std::string result;
|
||||
char *outDataPtr = nullptr;
|
||||
int totalByte = fillData(srcInfo.c_str(), srcInfo.length(), &outDataPtr);
|
||||
int totalByte = fillData(data, dataLen, &outDataPtr);
|
||||
|
||||
for (int i = 0; i < totalByte / (BIT_OF_GROUP / BIT_OF_BYTE); ++i)
|
||||
{
|
||||
|
|
|
@ -58,7 +58,7 @@ class Md5Encode
|
|||
};
|
||||
|
||||
public:
|
||||
static std::string encode(const std::string &srcInfo);
|
||||
static std::string encode(const char *data, const size_t dataLen);
|
||||
|
||||
protected:
|
||||
static uint32_t cycleMoveLeft(uint32_t srcNum, int bitNumToMove);
|
||||
|
|
Loading…
Reference in New Issue