Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/guides/included-middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ This middleware allows to set CORS policies by using `CORSHandler`. Once enabled

The CORS rules can be modified by first getting the middleware via `#!cpp auto& cors = app.get_middleware<crow::CORSHandler>();`. The rules can be set per URL prefix using `prefix()`, per blueprint using `blueprint()`, or globally via `global()`. These will return a `CORSRules` object which contains the actual rules for the prefix, blueprint, or application. For more details go [here](../reference/structcrow_1_1_c_o_r_s_handler.html).

`CORSRules` can be modified using the methods `origin()`, `methods()`, `headers()`, `max_age()`, `allow_credentials()`, or `ignore()`. For more details on these methods and what default values they take go [here](../reference/structcrow_1_1_c_o_r_s_rules.html).
`CORSRules` can be modified using the methods `origin()`, `origins()`, `methods()`, `headers()`, `max_age()`, `allow_credentials()`, or `ignore()`. For more details on these methods and what default values they take go [here](../reference/structcrow_1_1_c_o_r_s_rules.html).

```cpp
auto& cors = app.get_middleware<crow::CORSHandler>();
Expand All @@ -99,5 +99,5 @@ cors
.headers("X-Custom-Header", "Upgrade-Insecure-Requests")
.methods("POST"_method, "GET"_method)
.prefix("/cors")
.origin("example.com");
.origins("https://example.com", "https://api.example.com");
```
2 changes: 1 addition & 1 deletion examples/middlewares/example_cors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ int main()
.headers("X-Custom-Header", "Upgrade-Insecure-Requests")
.methods("POST"_method, "GET"_method)
.prefix("/cors")
.origin("example.com")
.origins("https://example.com", "https://api.example.com")
.prefix("/nocors")
.ignore();
// clang-format on
Expand Down
2 changes: 1 addition & 1 deletion include/crow/http_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ namespace crow
{
routing_handle_result_ = handler_->handle_initial(req_, res);
// if no route is found for the request method, return the response without parsing or processing anything further.
if (!routing_handle_result_->rule_index && !routing_handle_result_->catch_all)
if (!routing_handle_result_->rule_index && !routing_handle_result_->catch_all && req_.method != HTTPMethod::Options)
{
parser_.done();
need_to_call_after_handlers_ = true;
Expand Down
83 changes: 70 additions & 13 deletions include/crow/middlewares/cors.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#pragma once
#include <algorithm>
#include "crow/common.h"
#include "crow/http_request.h"
#include "crow/http_response.h"
Expand All @@ -17,6 +18,28 @@ namespace crow
CORSRules& origin(const std::string& origin)
{
origin_ = origin;
origins_.clear();
return *this;
}

/// Set dynamically selected Access-Control-Allow-Origin values
/// If the request Origin header matches one of these values, that
/// origin is echoed back
CORSRules& origins(const std::string& origin)
{
if (std::find(origins_.begin(), origins_.end(), origin) == origins_.end())
{
origins_.emplace_back(origin);
}
return *this;
}

/// Set dynamically selected Access-Control-Allow-Origin values
template<typename... Origins>
CORSRules& origins(const std::string& origin, Origins... origin_list)
{
origins(origin);
origins(origin_list...);
return *this;
}

Expand Down Expand Up @@ -123,34 +146,68 @@ namespace crow
{
if (ignore_) return;

const std::string& request_origin = req.get_header_value("Origin");
std::string allow_origin_value;
bool dynamic_origin = false;

if (!origins_.empty())
{
if (request_origin.empty()) return;
if (std::find(origins_.begin(), origins_.end(), request_origin) == origins_.end()) return;

allow_origin_value = request_origin;
dynamic_origin = true;
}
else if (allow_credentials_ && origin_ == "*" && !request_origin.empty())
{
// credentials are not compatible with a wildcard origin value
allow_origin_value = request_origin;
dynamic_origin = true;
}
else
{
allow_origin_value = origin_;
}

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-Expose-Headers", exposed_headers_, res);
set_header_no_override("Access-Control-Max-Age", max_age_, res);

bool origin_set = false;
set_header_no_override("Access-Control-Allow-Origin", allow_origin_value, res);
if (allow_credentials_ && allow_origin_value != "*")
{
set_header_no_override("Access-Control-Allow-Credentials", "true", res);
}
if (dynamic_origin)
{
add_vary_origin_no_override(res);
}
}

void add_vary_origin_no_override(crow::response& res)
{
const std::string& vary_header = get_header_value(res.headers, "Vary");
if (vary_header.empty())
{
res.add_header("Vary", "Origin");
return;
}

if (req.method != HTTPMethod::Options)
for (const std::string& current : utility::split(vary_header, ","))
{
if (allow_credentials_)
if (utility::string_equals(utility::trim(current), "Origin"))
{
set_header_no_override("Access-Control-Allow-Credentials", "true", res);
if (origin_ == "*")
{
set_header_no_override("Access-Control-Allow-Origin", req.get_header_value("Origin"), res);
origin_set = true;
}
return;
}
}

if( !origin_set){
set_header_no_override("Access-Control-Allow-Origin", origin_, res);
}
res.set_header("Vary", vary_header + ", Origin");
}

bool ignore_ = false;
// TODO: support multiple origins that are dynamically selected
std::string origin_ = "*";
std::vector<std::string> origins_;
std::string methods_ = "*";
std::string headers_ = "*";
std::string exposed_headers_;
Expand Down
41 changes: 36 additions & 5 deletions tests/unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,9 @@ TEST_CASE("middleware_cors")
.origin("test.test")
.prefix("/auth-origin")
.allow_credentials()
.prefix("/multi-origin")
.origins("https://a.test", "https://b.test")
.allow_credentials()
.prefix("/expose")
.expose("exposed-header")
.prefix("/nocors")
Expand All @@ -1259,6 +1262,15 @@ TEST_CASE("middleware_cors")
return "-";
});

CROW_ROUTE(app, "/multi-origin")
([&](const request&) {
return "-";
});

CROW_ROUTE(app, "/multi-origin").methods(crow::HTTPMethod::Options)([&](const request&) {
return "-";
});

CROW_ROUTE(app, "/expose")
([&](const request&) {
return "-";
Expand All @@ -1273,7 +1285,7 @@ TEST_CASE("middleware_cors")
auto _ = app.bindaddr(LOCALHOST_ADDRESS).port(port).run_async();
app.wait_for_server_start();
auto resp = HttpClient::request(LOCALHOST_ADDRESS, port,
"OPTIONS / HTTP/1.1\r\n\r\n");
"OPTIONS / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n");

CHECK(resp.find("Access-Control-Allow-Origin: *") != std::string::npos);

Expand All @@ -1291,9 +1303,26 @@ TEST_CASE("middleware_cors")
CHECK(resp.find("Access-Control-Allow-Credentials: true") != std::string::npos);

resp = HttpClient::request(LOCALHOST_ADDRESS, port,
"OPTIONS /auth-origin / HTTP/1.1 \r\n\r\n");
CHECK(resp.find("Access-Control-Allow-Origin: *") != std::string::npos);
CHECK(resp.find("Access-Control-Allow-Credentials: true") == std::string::npos);
"OPTIONS /auth-origin HTTP/1.0\r\nOrigin: test-client\r\nConnection: close\r\n\r\n");
CHECK(resp.find("Access-Control-Allow-Origin: test-client") != std::string::npos);
CHECK(resp.find("Access-Control-Allow-Credentials: true") != std::string::npos);

resp = HttpClient::request(LOCALHOST_ADDRESS, port,
"GET /multi-origin HTTP/1.0\r\nOrigin: https://a.test\r\nConnection: close\r\n\r\n");
CHECK(resp.find("Access-Control-Allow-Origin: https://a.test") != std::string::npos);
CHECK(resp.find("Access-Control-Allow-Credentials: true") != std::string::npos);
CHECK(resp.find("Vary: Origin") != std::string::npos);

resp = HttpClient::request(LOCALHOST_ADDRESS, port,
"OPTIONS /multi-origin HTTP/1.0\r\nOrigin: https://b.test\r\nConnection: close\r\n\r\n");
CHECK(resp.find("Access-Control-Allow-Origin: https://b.test") != std::string::npos);
CHECK(resp.find("Access-Control-Allow-Credentials: true") != std::string::npos);
CHECK(resp.find("Vary: Origin") != std::string::npos);

resp = HttpClient::request(LOCALHOST_ADDRESS, port,
"GET /multi-origin HTTP/1.0\r\nOrigin: https://c.test\r\nConnection: close\r\n\r\n");
CHECK(resp.find("Access-Control-Allow-Origin:") == std::string::npos);
CHECK(resp.find("Access-Control-Allow-Credentials:") == std::string::npos);

resp = HttpClient::request(LOCALHOST_ADDRESS, port,
"GET /expose\r\n\r\n");
Expand Down Expand Up @@ -2800,7 +2829,9 @@ TEST_CASE("option_header_passed_in_full")
};

std::string request =
"OPTIONS /echo HTTP/1.1\r\n";
"OPTIONS /echo HTTP/1.1\r\n"
"Host: localhost\r\n"
"Connection: close\r\n\r\n";

auto res = make_request(request);
CHECK(res.find(ServerName) != std::string::npos);
Expand Down
Loading