修复: 升级框架并完善报告导出
- 升级 Drogon 和 Trantor,修复畸形请求导致的连接计数泄漏\n- 增加第三方框架版本校验与自动重建\n- 完善完整报告导出和接口文档
This commit is contained in:
@@ -9,6 +9,7 @@ DROGON_TEST(Base64)
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
|
||||
CHECK(decoded == in);
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
|
||||
SUBSECTION(InvalidChars)
|
||||
{
|
||||
@@ -31,6 +32,7 @@ DROGON_TEST(Base64)
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw");
|
||||
CHECK(decoded == in);
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(LongString)
|
||||
@@ -46,6 +48,9 @@ DROGON_TEST(Base64)
|
||||
auto encoded = drogon::utils::base64Encode(in);
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(decoded == in);
|
||||
CHECK(out == encoded);
|
||||
CHECK(drogon::utils::isBase64(out));
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(URLSafe)
|
||||
@@ -55,6 +60,7 @@ DROGON_TEST(Base64)
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
|
||||
CHECK(decoded == in);
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(UnpaddedURLSafe)
|
||||
@@ -64,6 +70,7 @@ DROGON_TEST(Base64)
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw");
|
||||
CHECK(decoded == in);
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(LongURLSafe)
|
||||
@@ -77,5 +84,24 @@ DROGON_TEST(Base64)
|
||||
auto encoded = drogon::utils::base64Encode(in, true);
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(decoded == in);
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(emptyString)
|
||||
{
|
||||
auto encoded = "";
|
||||
CHECK(!drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(size1Padding)
|
||||
{
|
||||
auto encoded = "ZHJvZ29uIGZyYW1ld29=";
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(size1PaddingNotModulo4)
|
||||
{
|
||||
auto encoded = "ZHJvZ29uIGZyYW1ld29ya=";
|
||||
CHECK(!drogon::utils::isBase64(encoded));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,3 +66,195 @@ DROGON_TEST(ResquestSetCustomContentTypeString)
|
||||
req->setContentTypeString("thisdoesnotexist/unknown");
|
||||
CHECK(req->getContentType() == CT_CUSTOM);
|
||||
}
|
||||
|
||||
DROGON_TEST(HttpOptionsHeadersResponse)
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
auto resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(!resp);
|
||||
|
||||
req->setMethod(HttpMethod::Options);
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Vary") == "Origin");
|
||||
CHECK(resp->getHeader("Allow") == "OPTIONS");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Methods") == "");
|
||||
|
||||
req->attributes()->insert("drogon.corsMethods",
|
||||
std::string("GET, POST, OPTIONS"));
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Vary") == "Origin");
|
||||
CHECK(resp->getHeader("Allow") == "GET, POST, OPTIONS");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Methods") == "");
|
||||
|
||||
req->addHeader("Origin", "http://somepage");
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Vary") == "Origin");
|
||||
CHECK(resp->getHeader("Allow") == "GET, POST, OPTIONS");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Methods") == "");
|
||||
}
|
||||
|
||||
DROGON_TEST(HttpCorsHeadersResponse)
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->addHeader("Origin", "");
|
||||
req->addHeader("Access-Control-Request-Method", "OPTIONS");
|
||||
auto resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(!resp);
|
||||
|
||||
// empty origin -> error
|
||||
req->setMethod(HttpMethod::Options);
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k400BadRequest);
|
||||
|
||||
// null origin -> check if allowed or not
|
||||
req->addHeader("Origin", "null");
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k403Forbidden);
|
||||
resp = HttpResponse::newOptionsResponse(req, {}, true);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "null");
|
||||
|
||||
// normal origin but no requested method -> error
|
||||
req->addHeader("Origin", "http://somepage");
|
||||
req->addHeader("Access-Control-Request-Method", "");
|
||||
resp = HttpResponse::newOptionsResponse(req, {}, true);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k400BadRequest);
|
||||
|
||||
// valid CORS preflight request
|
||||
req->addHeader("Access-Control-Request-Method", "OPTIONS");
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Vary") == "Origin");
|
||||
CHECK(resp->getHeader("Allow") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "http://somepage");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Methods") == "OPTIONS");
|
||||
|
||||
// origin validator
|
||||
resp = HttpResponse::newOptionsResponse(req, [](std::string_view origin) {
|
||||
return origin == "http://somepage";
|
||||
});
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
resp = HttpResponse::newOptionsResponse(req, [](std::string_view origin) {
|
||||
return origin != "http://somepage";
|
||||
});
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k403Forbidden);
|
||||
|
||||
// unallowed method
|
||||
req->addHeader("Access-Control-Request-Method", "PUT");
|
||||
req->attributes()->insert("drogon.corsMethods",
|
||||
std::string("GET,POST,OPTIONS"));
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k405MethodNotAllowed);
|
||||
CHECK(resp->getHeader("Allow") == "GET,POST,OPTIONS");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Methods") ==
|
||||
"GET,POST,OPTIONS");
|
||||
|
||||
// allowed method
|
||||
req->addHeader("Access-Control-Request-Method", "GET");
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Allow") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "http://somepage");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Methods") ==
|
||||
"GET,POST,OPTIONS");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Credentials") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Private-Network") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Max-Age") == "");
|
||||
|
||||
// no restriction on requested headers
|
||||
req->addHeader("Access-Control-Request-Headers", "X-Foo, X-Bar");
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Headers") == "X-Bar,X-Foo");
|
||||
|
||||
// unallowed header
|
||||
resp = HttpResponse::newOptionsResponse(req, {"X-Foo"});
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k403Forbidden);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Headers") == "X-Foo");
|
||||
|
||||
// all requested headers allowed
|
||||
resp = HttpResponse::newOptionsResponse(req, {"X-Foo", "X-Bar"});
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Headers") == "X-Bar,X-Foo");
|
||||
|
||||
// allow credentials
|
||||
resp = HttpResponse::newOptionsResponse(req, nullptr, false, true);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Credentials") == "true");
|
||||
|
||||
// private network access
|
||||
req->addHeader("Access-Control-Request-Private-Network", "true");
|
||||
resp = HttpResponse::newOptionsResponse(req, nullptr, false, false, false);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Private-Network") == "");
|
||||
resp = HttpResponse::newOptionsResponse(req, nullptr, false, false, true);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Private-Network") == "true");
|
||||
|
||||
// CORS max age
|
||||
resp = HttpResponse::newOptionsResponse(
|
||||
req, nullptr, false, false, false, 600);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Max-Age") == "600");
|
||||
}
|
||||
|
||||
DROGON_TEST(AddHttpCorsHeaders)
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
// no Origin -> do nothing
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(Get);
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->addCorsHeaders(req, {"X-Foo"}, true);
|
||||
CHECK(resp->headers().empty());
|
||||
|
||||
// with Origin -> Allow-Origin + Vary (not overwritten) + Expose-Headers
|
||||
req->addHeader("Origin", "http://somepage");
|
||||
resp->addHeader("Vary", "X-SomeHeader");
|
||||
resp->addCorsHeaders(req, {"X-Foo"});
|
||||
CHECK(resp->getHeader("Vary") == "Origin,X-SomeHeader");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "http://somepage");
|
||||
CHECK(resp->getHeader("Access-Control-Expose-Headers") == "X-Foo");
|
||||
|
||||
// add a new exposed header
|
||||
resp->addCorsHeaders(req, {"X-Bar"});
|
||||
CHECK(resp->getHeader("Access-Control-Expose-Headers") == "X-Bar,X-Foo");
|
||||
// no duplicate Origin in Vary
|
||||
CHECK(resp->getHeader("Vary") == "Origin,X-SomeHeader");
|
||||
|
||||
// check credentials (true/false/unchanged)
|
||||
resp->addCorsHeaders(req, {}, true);
|
||||
CHECK(resp->getHeader("Access-Control-Expose-Headers") == "X-Bar,X-Foo");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Credentials") == "true");
|
||||
resp->addCorsHeaders(req, {}, false);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Credentials") == "");
|
||||
resp->addCorsHeaders(req, {}, true);
|
||||
resp->addCorsHeaders(req);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Credentials") == "true");
|
||||
}
|
||||
|
||||
DROGON_TEST(ClearHeaders)
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
// set a custom path to ensure it is not cleared
|
||||
req->setPath("/api/test");
|
||||
req->addHeader("X-Test", "value");
|
||||
req->addHeader("Authorization", "Bearer token");
|
||||
|
||||
CHECK(req->headers().size() == 2);
|
||||
CHECK(req->getHeader("X-Test") == "value");
|
||||
CHECK(req->getHeader("Authorization") == "Bearer token");
|
||||
|
||||
req->clearHeaders();
|
||||
|
||||
CHECK(req->headers().empty());
|
||||
// verify path unchanged
|
||||
CHECK(req->path() == "/api/test");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/HttpTypes.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include "../../lib/src/HttpRequestImpl.h"
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
// Helper: parse a method string through HttpRequestImpl::setMethod
|
||||
static std::pair<bool, HttpMethod> parseMethod(const std::string &str)
|
||||
{
|
||||
HttpRequestImpl req(nullptr);
|
||||
bool ok = req.setMethod(str.data(), str.data() + str.size());
|
||||
return {ok, req.method()};
|
||||
}
|
||||
|
||||
DROGON_TEST(StandardHttpMethods)
|
||||
{
|
||||
auto [ok, m] = parseMethod("GET");
|
||||
CHECK(ok);
|
||||
CHECK(m == Get);
|
||||
|
||||
std::tie(ok, m) = parseMethod("POST");
|
||||
CHECK(ok);
|
||||
CHECK(m == Post);
|
||||
|
||||
std::tie(ok, m) = parseMethod("PUT");
|
||||
CHECK(ok);
|
||||
CHECK(m == Put);
|
||||
|
||||
std::tie(ok, m) = parseMethod("DELETE");
|
||||
CHECK(ok);
|
||||
CHECK(m == Delete);
|
||||
|
||||
std::tie(ok, m) = parseMethod("HEAD");
|
||||
CHECK(ok);
|
||||
CHECK(m == Head);
|
||||
|
||||
std::tie(ok, m) = parseMethod("OPTIONS");
|
||||
CHECK(ok);
|
||||
CHECK(m == Options);
|
||||
|
||||
std::tie(ok, m) = parseMethod("PATCH");
|
||||
CHECK(ok);
|
||||
CHECK(m == Patch);
|
||||
}
|
||||
|
||||
DROGON_TEST(WebDavMethods)
|
||||
{
|
||||
auto [ok, m] = parseMethod("PROPFIND");
|
||||
CHECK(ok);
|
||||
CHECK(m == Propfind);
|
||||
|
||||
std::tie(ok, m) = parseMethod("MKCOL");
|
||||
CHECK(ok);
|
||||
CHECK(m == Mkcol);
|
||||
|
||||
std::tie(ok, m) = parseMethod("COPY");
|
||||
CHECK(ok);
|
||||
CHECK(m == Copy);
|
||||
|
||||
std::tie(ok, m) = parseMethod("MOVE");
|
||||
CHECK(ok);
|
||||
CHECK(m == Move);
|
||||
}
|
||||
|
||||
DROGON_TEST(WebDavMethodStrings)
|
||||
{
|
||||
CHECK(to_string_view(Propfind) == "PROPFIND");
|
||||
CHECK(to_string_view(Mkcol) == "MKCOL");
|
||||
CHECK(to_string_view(Copy) == "COPY");
|
||||
CHECK(to_string_view(Move) == "MOVE");
|
||||
}
|
||||
|
||||
// Helper: serialize a request and return the first line (method + path)
|
||||
static std::string serializeMethod(HttpMethod method)
|
||||
{
|
||||
HttpRequestImpl req(nullptr);
|
||||
req.setMethod(method);
|
||||
req.setPath("/test");
|
||||
trantor::MsgBuffer buf;
|
||||
req.appendToBuffer(&buf);
|
||||
std::string result(buf.peek(), buf.readableBytes());
|
||||
// Return just up to the first space after the method
|
||||
auto pos = result.find(' ');
|
||||
return result.substr(0, pos);
|
||||
}
|
||||
|
||||
DROGON_TEST(MethodSerialization)
|
||||
{
|
||||
CHECK(serializeMethod(Get) == "GET");
|
||||
CHECK(serializeMethod(Post) == "POST");
|
||||
CHECK(serializeMethod(Put) == "PUT");
|
||||
CHECK(serializeMethod(Delete) == "DELETE");
|
||||
CHECK(serializeMethod(Head) == "HEAD");
|
||||
CHECK(serializeMethod(Options) == "OPTIONS");
|
||||
CHECK(serializeMethod(Patch) == "PATCH");
|
||||
CHECK(serializeMethod(Propfind) == "PROPFIND");
|
||||
CHECK(serializeMethod(Mkcol) == "MKCOL");
|
||||
CHECK(serializeMethod(Copy) == "COPY");
|
||||
CHECK(serializeMethod(Move) == "MOVE");
|
||||
}
|
||||
|
||||
DROGON_TEST(InvalidMethodsRejected)
|
||||
{
|
||||
auto [ok, m] = parseMethod("INVALID");
|
||||
CHECK(!ok);
|
||||
CHECK(m == Invalid);
|
||||
|
||||
std::tie(ok, m) = parseMethod("LOCK");
|
||||
CHECK(!ok);
|
||||
CHECK(m == Invalid);
|
||||
|
||||
std::tie(ok, m) = parseMethod("");
|
||||
CHECK(!ok);
|
||||
CHECK(m == Invalid);
|
||||
|
||||
std::tie(ok, m) = parseMethod("G");
|
||||
CHECK(!ok);
|
||||
CHECK(m == Invalid);
|
||||
}
|
||||
Reference in New Issue
Block a user