mirror of
https://github.com/CrowCpp/Crow.git
synced 2024-06-07 21:10:44 +00:00
Merge branch 'master' into multipart_improvements
This commit is contained in:
commit
b02e1b1a73
37
examples/middlewares/cors.h
Normal file
37
examples/middlewares/cors.h
Normal file
@ -0,0 +1,37 @@
|
||||
#include "crow.h"
|
||||
#include "crow/middlewares/cors.h"
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
// Enable CORS
|
||||
crow::App<crow::CORSHandler> app;
|
||||
|
||||
// Customize CORS
|
||||
auto& cors = app.get_middleware<crow::CORSHandler>();
|
||||
|
||||
// clang-format off
|
||||
cors
|
||||
.global()
|
||||
.headers("X-Custom-Header", "Upgrade-Insecure-Requests")
|
||||
.methods("POST"_method, "GET"_method)
|
||||
.prefix("/cors")
|
||||
.origin("example.com")
|
||||
.prefix("/nocors")
|
||||
.ignore();
|
||||
// clang-format on
|
||||
|
||||
CROW_ROUTE(app, "/")
|
||||
([]() {
|
||||
return "Check Access-Control-Allow-Methods header";
|
||||
});
|
||||
|
||||
CROW_ROUTE(app, "/cors")
|
||||
([]() {
|
||||
return "Check Access-Control-Allow-Origin header";
|
||||
});
|
||||
|
||||
app.port(18080).run();
|
||||
|
||||
return 0;
|
||||
}
|
199
include/crow/middlewares/cors.h
Normal file
199
include/crow/middlewares/cors.h
Normal file
@ -0,0 +1,199 @@
|
||||
#pragma once
|
||||
#include "crow/http_request.h"
|
||||
#include "crow/http_response.h"
|
||||
#include "crow/routing.h"
|
||||
|
||||
namespace crow
|
||||
{
|
||||
struct CORSHandler;
|
||||
|
||||
// CORSRules is used for tuning cors policies
|
||||
struct CORSRules
|
||||
{
|
||||
friend struct crow::CORSHandler;
|
||||
|
||||
// Set Access-Control-Allow-Origin. Default is "*"
|
||||
CORSRules& origin(const std::string& origin)
|
||||
{
|
||||
origin_ = origin;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Set Access-Control-Allow-Methods. Default is "*"
|
||||
CORSRules& methods(crow::HTTPMethod method)
|
||||
{
|
||||
add_list_item(methods_, crow::method_name(method));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Set Access-Control-Allow-Methods. Default is "*"
|
||||
template<typename... Methods>
|
||||
CORSRules& methods(crow::HTTPMethod method, Methods... method_list)
|
||||
{
|
||||
add_list_item(methods_, crow::method_name(method));
|
||||
methods(method_list...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Set Access-Control-Allow-Headers. Default is "*"
|
||||
CORSRules& headers(const std::string& header)
|
||||
{
|
||||
add_list_item(headers_, header);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Set Access-Control-Allow-Headers. Default is "*"
|
||||
template<typename... Headers>
|
||||
CORSRules& headers(const std::string& header, Headers... header_list)
|
||||
{
|
||||
add_list_item(headers_, header);
|
||||
headers(header_list...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Set Access-Control-Max-Age. Default is none
|
||||
CORSRules& max_age(int max_age)
|
||||
{
|
||||
max_age_ = std::to_string(max_age);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Enable Access-Control-Allow-Credentials
|
||||
CORSRules& allow_credentials()
|
||||
{
|
||||
allow_credentials_ = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Ignore CORS and don't send any headers
|
||||
void ignore()
|
||||
{
|
||||
ignore_ = true;
|
||||
}
|
||||
|
||||
// Handle CORS on specific prefix path
|
||||
CORSRules& prefix(const std::string& prefix);
|
||||
|
||||
// Handle CORS for specific blueprint
|
||||
CORSRules& blueprint(const Blueprint& bp);
|
||||
|
||||
// Global CORS policy
|
||||
CORSRules& global();
|
||||
|
||||
private:
|
||||
CORSRules() = delete;
|
||||
CORSRules(CORSHandler* handler):
|
||||
handler_(handler) {}
|
||||
|
||||
// build comma separated list
|
||||
void add_list_item(std::string& list, const std::string& val)
|
||||
{
|
||||
if (list == "*") list = "";
|
||||
if (list.size() > 0) list += ", ";
|
||||
list += val;
|
||||
}
|
||||
|
||||
// Set header `key` to `value` if it is not set
|
||||
void set_header_no_override(const std::string& key, const std::string& value, crow::response& res)
|
||||
{
|
||||
if (value.size() == 0) return;
|
||||
if (!get_header_value(res.headers, key).empty()) return;
|
||||
res.add_header(key, value);
|
||||
}
|
||||
|
||||
// Set response headers
|
||||
void apply(crow::response& res)
|
||||
{
|
||||
if (ignore_) return;
|
||||
set_header_no_override("Access-Control-Allow-Origin", origin_, res);
|
||||
set_header_no_override("Access-Control-Allow-Methods", methods_, res);
|
||||
set_header_no_override("Access-Control-Allow-Headers", headers_, res);
|
||||
set_header_no_override("Access-Control-Max-Age", max_age_, res);
|
||||
if (allow_credentials_) set_header_no_override("Access-Control-Allow-Credentials", "true", res);
|
||||
}
|
||||
|
||||
bool ignore_ = false;
|
||||
// TODO: support multiple origins that are dynamically selected
|
||||
std::string origin_ = "*";
|
||||
std::string methods_ = "*";
|
||||
std::string headers_ = "*";
|
||||
std::string max_age_;
|
||||
bool allow_credentials_ = false;
|
||||
|
||||
CORSHandler* handler_;
|
||||
};
|
||||
|
||||
/// CORSHandler is a global middleware for setting CORS headers.
|
||||
///
|
||||
/// By default, it sets Access-Control-Allow-Origin/Methods/Headers to "*".
|
||||
/// The default behaviour can be changed with the `global()` cors rule.
|
||||
/// Additional rules for prexies can be added with `prefix()`.
|
||||
struct CORSHandler
|
||||
{
|
||||
struct context
|
||||
{};
|
||||
|
||||
void before_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/)
|
||||
{}
|
||||
|
||||
void after_handle(crow::request& req, crow::response& res, context& /*ctx*/)
|
||||
{
|
||||
auto& rule = find_rule(req.url);
|
||||
rule.apply(res);
|
||||
}
|
||||
|
||||
// Handle CORS on specific prefix path
|
||||
CORSRules& prefix(const std::string& prefix)
|
||||
{
|
||||
rules.emplace_back(prefix, CORSRules(this));
|
||||
return rules.back().second;
|
||||
}
|
||||
|
||||
// Handle CORS for specific blueprint
|
||||
CORSRules& blueprint(const Blueprint& bp)
|
||||
{
|
||||
rules.emplace_back(bp.prefix(), CORSRules(this));
|
||||
return rules.back().second;
|
||||
}
|
||||
|
||||
// Global CORS policy
|
||||
CORSRules& global()
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
|
||||
private:
|
||||
CORSRules& find_rule(const std::string& path)
|
||||
{
|
||||
// TODO: use a trie in case of many rules
|
||||
for (auto& rule : rules)
|
||||
{
|
||||
// Check if path starts with a rules prefix
|
||||
if (path.rfind(rule.first, 0) == 0)
|
||||
{
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return default_;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, CORSRules>> rules;
|
||||
CORSRules default_ = CORSRules(this);
|
||||
};
|
||||
|
||||
CORSRules& CORSRules::prefix(const std::string& prefix)
|
||||
{
|
||||
return handler_->prefix(prefix);
|
||||
}
|
||||
|
||||
CORSRules& CORSRules::blueprint(const Blueprint& bp)
|
||||
{
|
||||
return handler_->blueprint(bp);
|
||||
}
|
||||
|
||||
CORSRules& CORSRules::global()
|
||||
{
|
||||
return handler_->global();
|
||||
}
|
||||
|
||||
} // namespace crow
|
@ -12,6 +12,7 @@
|
||||
#include "catch.hpp"
|
||||
#include "crow.h"
|
||||
#include "crow/middlewares/cookie_parser.h"
|
||||
#include "crow/middlewares/cors.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace crow;
|
||||
@ -1521,6 +1522,87 @@ TEST_CASE("middleware_cookieparser")
|
||||
app.stop();
|
||||
} // middleware_cookieparser
|
||||
|
||||
|
||||
TEST_CASE("middleware_cors")
|
||||
{
|
||||
static char buf[5012];
|
||||
|
||||
App<crow::CORSHandler> app;
|
||||
|
||||
auto& cors = app.get_middleware<crow::CORSHandler>();
|
||||
// clang-format off
|
||||
cors
|
||||
.prefix("/origin")
|
||||
.origin("test.test")
|
||||
.prefix("/nocors")
|
||||
.ignore();
|
||||
// clang-format on
|
||||
|
||||
CROW_ROUTE(app, "/")
|
||||
([&](const request&) {
|
||||
return "-";
|
||||
});
|
||||
|
||||
CROW_ROUTE(app, "/origin")
|
||||
([&](const request&) {
|
||||
return "-";
|
||||
});
|
||||
|
||||
CROW_ROUTE(app, "/nocors/path")
|
||||
([&](const request&) {
|
||||
return "-";
|
||||
});
|
||||
|
||||
auto _ = async(launch::async,
|
||||
[&] {
|
||||
app.bindaddr(LOCALHOST_ADDRESS).port(45451).run();
|
||||
});
|
||||
|
||||
app.wait_for_server_start();
|
||||
asio::io_service is;
|
||||
|
||||
{
|
||||
asio::ip::tcp::socket c(is);
|
||||
c.connect(asio::ip::tcp::endpoint(
|
||||
asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451));
|
||||
|
||||
c.send(asio::buffer("GET /\r\n\r\n"));
|
||||
|
||||
c.receive(asio::buffer(buf, 2048));
|
||||
c.close();
|
||||
|
||||
CHECK(std::string(buf).find("Access-Control-Allow-Origin: *") != std::string::npos);
|
||||
}
|
||||
|
||||
{
|
||||
asio::ip::tcp::socket c(is);
|
||||
c.connect(asio::ip::tcp::endpoint(
|
||||
asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451));
|
||||
|
||||
c.send(asio::buffer("GET /origin\r\n\r\n"));
|
||||
|
||||
c.receive(asio::buffer(buf, 2048));
|
||||
c.close();
|
||||
|
||||
CHECK(std::string(buf).find("Access-Control-Allow-Origin: test.test") != std::string::npos);
|
||||
}
|
||||
|
||||
{
|
||||
asio::ip::tcp::socket c(is);
|
||||
c.connect(asio::ip::tcp::endpoint(
|
||||
asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451));
|
||||
|
||||
c.send(asio::buffer("GET /nocors/path\r\n\r\n"));
|
||||
|
||||
c.receive(asio::buffer(buf, 2048));
|
||||
c.close();
|
||||
|
||||
CHECK(std::string(buf).find("Access-Control-Allow-Origin:") == std::string::npos);
|
||||
}
|
||||
|
||||
app.stop();
|
||||
} // middleware_cors
|
||||
|
||||
TEST_CASE("bug_quick_repeated_request")
|
||||
{
|
||||
static char buf[2048];
|
||||
|
Loading…
Reference in New Issue
Block a user