Use string_view to parse multipart/form-data requests (#469)

This commit is contained in:
An Tao 2020-06-10 11:11:24 +08:00 committed by GitHub
parent be6f0966d5
commit e7b7618c37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 316 additions and 155 deletions

View File

@ -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

View File

@ -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)
{

View File

@ -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

View File

@ -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.
/**

161
lib/src/HttpFileImpl.cc Normal file
View File

@ -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))
{
}

100
lib/src/HttpFileImpl.h Normal file
View File

@ -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

View File

@ -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_);
}

View File

@ -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
}

View File

@ -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)
{

View File

@ -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);