修复: 升级框架并完善报告导出
- 升级 Drogon 和 Trantor,修复畸形请求导致的连接计数泄漏\n- 增加第三方框架版本校验与自动重建\n- 完善完整报告导出和接口文档
This commit is contained in:
@@ -148,4 +148,46 @@ class HttpCoroMiddleware : public DrObject<T>, public HttpMiddlewareBase
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Simple middleware that tags OPTIONS requests
|
||||
* @details It adds the attribute "drogon.customCORShandling" to the request, so
|
||||
* that HttpServer does not handle CORS for them internally.
|
||||
*
|
||||
* This allows custom CORS handling via the path handlers.
|
||||
* For example to restrict the origins, headers allowed, specify a max age to
|
||||
* avoid OPTIONS on every request, etc.
|
||||
*
|
||||
* Just register it:
|
||||
* 1. globally via
|
||||
* app().registerMiddleware(std::make_shared<drogon::HttpOptionsMiddleware>())
|
||||
* 2. on every path handlers that need non-default handling, with
|
||||
* ADD_METHOD_TO(..., drogon::Options, "drogon::HttpOptionsMiddleware")
|
||||
*/
|
||||
template <class Derived, bool AutoCreation = true>
|
||||
class HttpOptionsMiddlewareImpl
|
||||
: public drogon::HttpMiddleware<Derived, AutoCreation>
|
||||
{
|
||||
public:
|
||||
void invoke(const HttpRequestPtr &req,
|
||||
MiddlewareNextCallback &&nextCb,
|
||||
MiddlewareCallback &&mcb) override
|
||||
{
|
||||
// Tag OPTIONS
|
||||
if (req->method() == drogon::HttpMethod::Options)
|
||||
req->attributes()->insert("drogon.customCORShandling", true);
|
||||
// continue with next middleware (no post-processing here)
|
||||
nextCb(std::move(mcb));
|
||||
}
|
||||
};
|
||||
|
||||
class HttpOptionsMiddlewareAuto
|
||||
: public HttpOptionsMiddlewareImpl<HttpOptionsMiddlewareAuto, true>
|
||||
{
|
||||
};
|
||||
|
||||
class HttpOptionsMiddleware
|
||||
: public HttpOptionsMiddlewareImpl<HttpOptionsMiddleware, false>
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace drogon
|
||||
|
||||
@@ -159,6 +159,9 @@ class DROGON_EXPORT HttpRequest
|
||||
*/
|
||||
virtual void removeHeader(std::string key) = 0;
|
||||
|
||||
// Clear all HTTP headers
|
||||
virtual void clearHeaders() = 0;
|
||||
|
||||
/// Get the cookie string identified by the field parameter
|
||||
virtual const std::string &getCookie(const std::string &field) const = 0;
|
||||
|
||||
@@ -415,6 +418,8 @@ class DROGON_EXPORT HttpRequest
|
||||
virtual void setMethod(const HttpMethod method) = 0;
|
||||
|
||||
/// Set the path of the request
|
||||
/// @note The path is automatically encoded. use
|
||||
/// @c setPathEncode(false) to avoid this.
|
||||
virtual void setPath(const std::string &path) = 0;
|
||||
virtual void setPath(std::string &&path) = 0;
|
||||
|
||||
@@ -432,6 +437,20 @@ class DROGON_EXPORT HttpRequest
|
||||
virtual void setParameter(const std::string &key,
|
||||
const std::string &value) = 0;
|
||||
|
||||
/**
|
||||
* Set the parameter to the query,
|
||||
* regardless of the HTTP method or content type
|
||||
*/
|
||||
virtual void setQueryParameter(const std::string &key,
|
||||
const std::string &value) = 0;
|
||||
/**
|
||||
* Set the parameter to the request body.
|
||||
* @warning The content type must be @c application/x-www-form-urlencoded
|
||||
* or @c multipart/form-data
|
||||
*/
|
||||
virtual void setBodyParameter(const std::string &key,
|
||||
const std::string &value) = 0;
|
||||
|
||||
/// Set or get the content type
|
||||
virtual void setContentTypeCode(const ContentType type) = 0;
|
||||
|
||||
@@ -501,6 +520,37 @@ class DROGON_EXPORT HttpRequest
|
||||
return toRequest(std::forward<T>(obj));
|
||||
}
|
||||
|
||||
/*! \brief Check if the request is a CORS request.
|
||||
* \details It should contain:
|
||||
* - Origin: origination page
|
||||
* \returns true if the Origin header is present
|
||||
*/
|
||||
inline bool isCorsRequest() const
|
||||
{
|
||||
// Check presence of required headers
|
||||
return headers().find("origin") != headers().end();
|
||||
}
|
||||
|
||||
/*! \brief Check if the request is a CORS pre-flight request.
|
||||
* \details Check if the method of the request is OPTIONS and if it is
|
||||
* a CORS pre-flight request.\n
|
||||
* It should contain:
|
||||
* - Origin: origination page
|
||||
* - Access-Control-Request-Method: method to be used in the
|
||||
* actual request
|
||||
* \returns true if the method is OPTIONS and the required CORS pre-flight
|
||||
* headers are present
|
||||
*/
|
||||
inline bool isCorsPreflightRequest() const
|
||||
{
|
||||
if (method() != HttpMethod::Options)
|
||||
return false;
|
||||
// Check presence of required headers
|
||||
return isCorsRequest() &&
|
||||
headers().find("access-control-request-method") !=
|
||||
headers().end();
|
||||
}
|
||||
|
||||
virtual bool isOnSecureConnection() const noexcept = 0;
|
||||
virtual void setContentTypeString(const char *typeString,
|
||||
size_t typeStringLength) = 0;
|
||||
|
||||
+143
-2
@@ -161,6 +161,12 @@ class DROGON_EXPORT HttpResponse
|
||||
setCustomStatusCode(code, message.data(), message.length());
|
||||
}
|
||||
|
||||
/// Set whether the response should be compress.
|
||||
virtual void setAllowCompression(bool allow) = 0;
|
||||
|
||||
/// Get whether the response allow compression.
|
||||
virtual bool allowCompression() const = 0;
|
||||
|
||||
/// Get the creation timestamp of the response.
|
||||
virtual const trantor::Date &creationDate() const = 0;
|
||||
|
||||
@@ -552,6 +558,141 @@ class DROGON_EXPORT HttpResponse
|
||||
return toResponse(std::forward<T>(obj));
|
||||
}
|
||||
|
||||
/*! \brief Create an OPTIONS or CORS pre-flight response
|
||||
* \details If the request is not an OPTIONS request, returns a NULL
|
||||
* response\n
|
||||
* If it is a generic OPTIONS request, returns a 204 No Content
|
||||
* response with the Allow header\n
|
||||
* If it is a CORS pre-flight request, returns a 204 No Content
|
||||
* response with the CORS headers set
|
||||
*
|
||||
* Other status codes for CORS pre-flight answers:
|
||||
* - 400 Bad Request: if the request is malformed (missing
|
||||
* required headers)
|
||||
* - 403 Forbidden: if the Origin is not allowed + reason
|
||||
* in a X-Cors-Error header
|
||||
* - 403 Forbidden: if one of the headers in
|
||||
* Access-Control-Request-Headers is not allowed + reason in
|
||||
* a X-Cors-Error header
|
||||
* - 405 Method Not Allowed: if the requested method is
|
||||
* not allowed
|
||||
* \note CORS is a browser-side security mechanism.\n
|
||||
* Do not rely on Origin for authentication/authorization:
|
||||
* non-browser clients can spoof or omit it.\n
|
||||
* Enforce access control independently.
|
||||
* \param[in] request Drogon (OPTIONS) request
|
||||
* \param[in] allowedHeaders Set of allowed headers (for
|
||||
* Access-Control-Allow-Headers header)\n
|
||||
* (headers allowed by the controller path
|
||||
* handler)
|
||||
* \param[in] originValidator Function to validate the Origin header value
|
||||
* (allow the origin or not)\n
|
||||
* If allowCredentials is true, originValidator
|
||||
* _SHOULD_ enforce a strict allowlist
|
||||
* \param[in] allowNullOrigin Should be true to accept the "Origin: null"
|
||||
* header\n
|
||||
* (set for local file:// pages, sandboxed
|
||||
* iframes, opaque origins, data: URIs)
|
||||
* \param[in] allowCredentials Should be true to add the header
|
||||
* "Access-Control-Allow-Credentials: true"
|
||||
* (controls whether the browser may include
|
||||
* credentials such as cookies, HTTP auth, or
|
||||
* client certificates)\n
|
||||
* Note: Authorization (bearer) is not a
|
||||
* credential header; allow it via
|
||||
* allowedHeaders when needed
|
||||
* \param[in] allowPNA Should be true to accept the header
|
||||
* "Access-Control-Request-Private-Network"
|
||||
* (when a page from a less private address
|
||||
* space is trying to reach a more private
|
||||
* one, like internet -> intranet)\n
|
||||
* Note: specific to Chromium & derivatives
|
||||
* (Edge, Opera, Brave, ...), not in Firefox
|
||||
* or Safari
|
||||
* \param[in] maxAgeSeconds If set, adds the "Access-Control-Max-Age"
|
||||
* header with the given value (in seconds,
|
||||
* how long the results of a preflight
|
||||
* request can be cached by the navigator)
|
||||
* \returns the OPTIONS or CORS pre-flight response, or a null pointer if
|
||||
* the request is not an OPTIONS request
|
||||
*/
|
||||
static HttpResponsePtr newOptionsResponse(
|
||||
const HttpRequestPtr &request,
|
||||
const std::function<bool(std::string_view)> &originValidator = nullptr,
|
||||
bool allowNullOrigin = false,
|
||||
bool allowCredentials = false,
|
||||
bool allowPNA = true,
|
||||
std::optional<unsigned int> maxAgeSeconds = {},
|
||||
const std::optional<std::set<std::string_view>> &allowedHeaders =
|
||||
std::nullopt);
|
||||
|
||||
/*! \copydoc newOptionsResponse(const HttpRequestPtr&,
|
||||
* const std::function<bool(std::string_view)>&,
|
||||
* bool, bool, bool,
|
||||
* std::optional<unsigned int>,
|
||||
* const std::optional<std::set<std::string_view>>&)
|
||||
* \remarks Helper when specifying the allowed headers, when other
|
||||
* parameters may be default, to avoid having to specify them all
|
||||
*/
|
||||
inline static HttpResponsePtr newOptionsResponse(
|
||||
const HttpRequestPtr &request,
|
||||
const std::set<std::string_view> &allowedHeaders,
|
||||
const std::function<bool(std::string_view)> &originValidator = nullptr,
|
||||
bool allowNullOrigin = false,
|
||||
bool allowCredentials = false,
|
||||
bool allowPNA = true,
|
||||
std::optional<unsigned int> maxAgeSeconds = {})
|
||||
{
|
||||
return newOptionsResponse(request,
|
||||
originValidator,
|
||||
allowNullOrigin,
|
||||
allowCredentials,
|
||||
allowPNA,
|
||||
maxAgeSeconds,
|
||||
allowedHeaders);
|
||||
}
|
||||
|
||||
/*! \brief Add CORS headers to a response
|
||||
* \details Adds the CORS headers to a response for a normal request (a
|
||||
* CORS request but not a CORS preflight request):
|
||||
* - does nothing if it's an OPTIONS request, or
|
||||
* - if it's not a CORS request, or
|
||||
* - if it's a CORS preflight request
|
||||
* Else:
|
||||
* - adds Access-Control-Allow-Origin (if not yet present)
|
||||
* - adds Origin to the Vary header,
|
||||
* - sets or clears Access-Control-Allow-Credentials (if
|
||||
* allowCredentials is set)
|
||||
* - completes Access-Control-Expose-Headers
|
||||
* \param[in] request Drogon request (to get Origin)
|
||||
* \param[in] allowCredentials If set and true, adds the
|
||||
* "Access-Control-Allow-Credentials: true
|
||||
* header"\n
|
||||
* If set and false, removes the
|
||||
* "Access-Control-Allow-Credentials" header\n
|
||||
* If not set, leaves the
|
||||
* "Access-Control-Allow-Credentials" header
|
||||
* untouched\n
|
||||
* *MUST MATCH THE newOptionsResponse()
|
||||
* PRE-FLIGHT RESPONSE VALUE*
|
||||
* \param[in] exposedHeaders Set of exposed headers (for
|
||||
* Access-Control-Expose-Headers header)\n
|
||||
* These are the headers allowed to be exposed
|
||||
* to javascript by the remote browser\n
|
||||
* Note: they are *APPENDED* to any already
|
||||
* present in the response, they are not
|
||||
* REPLACED.\n
|
||||
* This allows to complete them in the
|
||||
* controller path handler.\n
|
||||
* If you want to REPLACE them, remove the
|
||||
* header before calling this function.
|
||||
* \note may be use both in the controller path handler and in a
|
||||
* pre-sending advice
|
||||
*/
|
||||
void addCorsHeaders(const HttpRequestPtr &request,
|
||||
const std::set<std::string_view> &exposedHeaders = {},
|
||||
const std::optional<bool> &allowCredentials = {});
|
||||
|
||||
/**
|
||||
* @brief If the response is a file response (i.e. created by
|
||||
* newFileResponse) returns the path on the filesystem. Otherwise a
|
||||
@@ -560,9 +701,9 @@ class DROGON_EXPORT HttpResponse
|
||||
virtual const std::string &sendfileName() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns the range of the file response as a pair ot size_t
|
||||
* @brief Returns the range of the file response as a pair of size_t
|
||||
* (offset, length). Length of 0 means the entire file is sent. Behavior of
|
||||
* this function is undefined if the response if not a file response
|
||||
* this function is undefined if the response is not a file response
|
||||
*/
|
||||
using SendfileRange = std::pair<size_t, size_t>; // { offset, length }
|
||||
virtual const SendfileRange &sendfileRange() const = 0;
|
||||
|
||||
@@ -195,6 +195,10 @@ enum HttpMethod
|
||||
Delete,
|
||||
Options,
|
||||
Patch,
|
||||
Propfind,
|
||||
Mkcol,
|
||||
Copy,
|
||||
Move,
|
||||
Invalid
|
||||
};
|
||||
|
||||
@@ -280,6 +284,14 @@ inline std::string_view to_string_view(drogon::HttpMethod method)
|
||||
return "OPTIONS";
|
||||
case drogon::HttpMethod::Patch:
|
||||
return "PATCH";
|
||||
case drogon::HttpMethod::Propfind:
|
||||
return "PROPFIND";
|
||||
case drogon::HttpMethod::Mkcol:
|
||||
return "MKCOL";
|
||||
case drogon::HttpMethod::Copy:
|
||||
return "COPY";
|
||||
case drogon::HttpMethod::Move:
|
||||
return "MOVE";
|
||||
default:
|
||||
return "INVALID";
|
||||
}
|
||||
|
||||
@@ -56,6 +56,27 @@ class UploadFile
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructor
|
||||
/**
|
||||
* @param data Pointer to the data
|
||||
* @param len Data length in bytes
|
||||
* @param fileName The file name provided to the server.
|
||||
* @param itemName The item name on the browser form.
|
||||
* @param contentType The Mime content type for the part
|
||||
*/
|
||||
explicit UploadFile(const void *data,
|
||||
const size_t len,
|
||||
const std::string &fileName = "memory.bin",
|
||||
const std::string &itemName = "file",
|
||||
ContentType contentType = CT_APPLICATION_OCTET_STREAM)
|
||||
: data_(data),
|
||||
len_(len),
|
||||
fileName_(fileName),
|
||||
itemName_(itemName),
|
||||
contentType_(contentType)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string &path() const
|
||||
{
|
||||
return path_;
|
||||
@@ -76,7 +97,19 @@ class UploadFile
|
||||
return contentType_;
|
||||
}
|
||||
|
||||
const void *data() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
size_t dataLength() const
|
||||
{
|
||||
return len_;
|
||||
}
|
||||
|
||||
private:
|
||||
const void *data_ = nullptr;
|
||||
size_t len_ = 0;
|
||||
std::string path_;
|
||||
std::string fileName_;
|
||||
std::string itemName_;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <drogon/plugins/Plugin.h>
|
||||
#include <trantor/utils/AsyncFileLogger.h>
|
||||
#include <vector>
|
||||
#include <regex>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
|
||||
+162
-2
@@ -124,6 +124,165 @@ DROGON_EXPORT std::set<std::string> splitStringToSet(
|
||||
const std::string &str,
|
||||
const std::string &separator);
|
||||
|
||||
/*! \brief Compare two string_views for equality, ignoring case.
|
||||
* \warning This is locale dependent
|
||||
* \param[in] str1 The first string_view.
|
||||
* \param[in] str2 The second string_view.
|
||||
* \return true if the string_views are equal, ignoring case; false otherwise.
|
||||
*/
|
||||
inline bool ci_equals(std::string_view str1, std::string_view str2)
|
||||
{
|
||||
if (str1.size() != str2.size())
|
||||
return false;
|
||||
return std::equal(str1.begin(),
|
||||
str1.end(),
|
||||
str2.begin(),
|
||||
[](unsigned char a, unsigned char b) {
|
||||
return std::tolower(a) == std::tolower(b);
|
||||
});
|
||||
}
|
||||
|
||||
/*! \details Trim leading and trailing spaces and tabs from a string_view,
|
||||
* modifying it.
|
||||
* \param[in,out] str The string_view to trim.
|
||||
* \return The trimmed string_view.
|
||||
*/
|
||||
inline std::string_view &trim_inplace(std::string_view &str)
|
||||
{
|
||||
auto pos = str.find_first_not_of(" \t");
|
||||
// defeat Windows macro "min"
|
||||
str.remove_prefix((std::min)(pos, str.size()));
|
||||
if (str.empty())
|
||||
return str;
|
||||
pos = str.find_last_not_of(" \t");
|
||||
str.remove_suffix(str.size() - pos - 1);
|
||||
return str;
|
||||
}
|
||||
|
||||
/*! \brief Trim leading and trailing spaces and tabs from a string_view.
|
||||
* \param[in] str The string_view to trim.
|
||||
* \return A string_view with leading and trailing spaces and tabs removed.
|
||||
*/
|
||||
inline std::string_view trim(std::string_view str)
|
||||
{
|
||||
return trim_inplace(str);
|
||||
}
|
||||
|
||||
/*! \brief Trim leading and trailing spaces and tabs from a rvalue string.
|
||||
* \param[in] str The string to trim.
|
||||
* \return The string with leading and trailing spaces and tabs removed.
|
||||
*/
|
||||
inline std::string trim(std::string &&str)
|
||||
{
|
||||
auto pos = str.find_last_not_of(" \t");
|
||||
if (pos == std::string::npos)
|
||||
return {};
|
||||
str.resize(pos + 1);
|
||||
pos = str.find_first_not_of(" \t");
|
||||
if (pos > 0)
|
||||
str.erase(0, pos);
|
||||
return str;
|
||||
}
|
||||
|
||||
/*! \brief Split a string_view into a vector of string_views.
|
||||
* \param[in] str The string_view to split.
|
||||
* \param[in] separator The separator to use for splitting.
|
||||
* \param[in] trimValues Whether to trim whitespace from the resulting
|
||||
* string_views.
|
||||
* \param[in] acceptEmptyString Whether to include empty strings in the result.
|
||||
* \return A vector of string_views obtained by splitting the input
|
||||
* string_view.
|
||||
*/
|
||||
inline std::vector<std::string_view> splitStringView(
|
||||
std::string_view str,
|
||||
std::string_view separator,
|
||||
bool trimValues = true,
|
||||
bool acceptEmptyString = false)
|
||||
{
|
||||
std::vector<std::string_view> result;
|
||||
if (separator.empty())
|
||||
{
|
||||
if (trimValues)
|
||||
trim_inplace(str);
|
||||
if (acceptEmptyString || !str.empty())
|
||||
result.push_back(str);
|
||||
return result;
|
||||
}
|
||||
size_t start = 0;
|
||||
size_t end = 0;
|
||||
while ((end = str.find(separator, start)) != std::string_view::npos)
|
||||
{
|
||||
auto token = str.substr(start, end - start);
|
||||
if (trimValues)
|
||||
trim_inplace(token);
|
||||
if (acceptEmptyString || !token.empty())
|
||||
result.push_back(token);
|
||||
start = end + separator.size();
|
||||
}
|
||||
auto token = str.substr(start);
|
||||
if (trimValues)
|
||||
trim_inplace(token);
|
||||
if (acceptEmptyString || !token.empty())
|
||||
{
|
||||
result.push_back(token);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*! \brief Split a string_view into a set of string_views.
|
||||
* \copyparams splitStringView
|
||||
* \return A set of (unique) string_views obtained by splitting the input
|
||||
* string_view.
|
||||
* \note Uniqueness is case-sensitive: "A" and "a" are considered different
|
||||
* values.
|
||||
*/
|
||||
inline std::set<std::string_view> splitStringViewToSet(
|
||||
std::string_view str,
|
||||
std::string_view separator,
|
||||
bool trimValues = true,
|
||||
bool acceptEmptyString = false)
|
||||
{
|
||||
auto v = splitStringView(str, separator, trimValues, acceptEmptyString);
|
||||
return std::set<std::string_view>(v.begin(), v.end());
|
||||
}
|
||||
|
||||
/*! \brief Join a vector of string_view into a string.
|
||||
* \param[in] strs The vector of string_views to join.
|
||||
* \param[in] separator The separator to use between string_views.
|
||||
* \return A single string obtained by joining the input string_views with the
|
||||
* specified separator.
|
||||
* \note Empty values are skipped.
|
||||
*/
|
||||
inline std::string joinStringViews(const std::vector<std::string_view> &strs,
|
||||
std::string_view separator)
|
||||
{
|
||||
std::string result;
|
||||
for (std::string_view str : strs)
|
||||
{
|
||||
if (trim_inplace(str).empty())
|
||||
continue;
|
||||
if (!result.empty())
|
||||
result.append(separator);
|
||||
result.append(str);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*! \brief Join a set of string_view into a string.
|
||||
* \param[in] strs The set of string_views to join.
|
||||
* \param[in] separator The separator to use between string_views.
|
||||
* \return A single string obtained by joining the input string_views with the
|
||||
* specified separator.
|
||||
* \note Empty values are skipped.
|
||||
*/
|
||||
inline std::string joinStringViews(const std::set<std::string_view> &strs,
|
||||
std::string_view separator)
|
||||
{
|
||||
return joinStringViews(std::vector<std::string_view>{strs.begin(),
|
||||
strs.end()},
|
||||
separator);
|
||||
}
|
||||
|
||||
/// Get UUID string.
|
||||
DROGON_EXPORT std::string getUuid(bool lowercase = true);
|
||||
|
||||
@@ -497,7 +656,8 @@ T fromString(const std::string &p) noexcept(false)
|
||||
// ("1a" should not return 1)
|
||||
if (pos != p.size())
|
||||
throw std::invalid_argument("Invalid value");
|
||||
if ((v < static_cast<long double>((std::numeric_limits<T>::min)())) ||
|
||||
if ((v <
|
||||
static_cast<long double>((std::numeric_limits<T>::lowest)())) ||
|
||||
(v > static_cast<long double>((std::numeric_limits<T>::max)())))
|
||||
throw std::out_of_range("Value out of range");
|
||||
return static_cast<T>(v);
|
||||
@@ -516,7 +676,7 @@ T fromString(const std::string &p) noexcept(false)
|
||||
// throw if the whole string could not be parsed
|
||||
// ("1a" should not return 1)
|
||||
if (!ss.eof())
|
||||
std::runtime_error("Bad type conversion");
|
||||
throw std::runtime_error("Bad type conversion");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user