diff --git a/winixd/core/config.cpp b/winixd/core/config.cpp index be3e71b..ec768c5 100644 --- a/winixd/core/config.cpp +++ b/winixd/core/config.cpp @@ -350,6 +350,9 @@ void Config::AssignValues() add_header_cache_no_store_in_htmx_request = Bool(L"add_header_cache_no_store_in_htmx_request", true); request_queue_job_limit = Size(L"request_queue_job_limit", 1024); + + allow_all_cors_origins = Bool(L"allow_all_cors_origins", false); + ListText(L"allowed_cors_origins", allowed_cors_origins); } diff --git a/winixd/core/config.h b/winixd/core/config.h index 6d5400a..0616b3c 100644 --- a/winixd/core/config.h +++ b/winixd/core/config.h @@ -976,7 +976,17 @@ public: // if the limit is reached then the http status 503 Service Unavailable is returned size_t request_queue_job_limit; + // whether or not all origins are allowed in cors requests + // default: false; + // if false then we check allowed_cors_origins table to check whether the origin is available (origin is sent in Origin header), + // if allow_all_cors_origins is false and allowed_cors_origins is empty then by default we do not allow cors requests + // (but you can still allow it in your function/controller by overriding IsCorsOriginAvailable(...) method) + bool allow_all_cors_origins; + // list of allowed origins in cors requests + // used only if allow_all_cors_origins is false + // default: empty + std::vector allowed_cors_origins; Config(); diff --git a/winixd/core/misc.cpp b/winixd/core/misc.cpp index bbb7a89..0bde9ad 100644 --- a/winixd/core/misc.cpp +++ b/winixd/core/misc.cpp @@ -1628,8 +1628,23 @@ void slice_by(const std::wstring & str, wchar_t c, std::vector & o } +bool is_in_list(const std::wstring & item, const std::vector & list) +{ + return is_in_list_generic(item, list); +} +bool is_in_list(const std::wstring & item, const std::list & list) +{ + return is_in_list_generic(item, list); +} + + +bool is_in_list(const std::wstring & item, const std::set & list) +{ + return is_in_list_generic(item, list); +} + } // namespace Winix diff --git a/winixd/core/misc.h b/winixd/core/misc.h index f20f177..32f0e36 100644 --- a/winixd/core/misc.h +++ b/winixd/core/misc.h @@ -36,6 +36,8 @@ #define headerfile_winix_core_misc #include +#include +#include #include #include #include @@ -1032,6 +1034,28 @@ void timespec_to_stream_with_unit(timespec & val, pt::Stream & stream); void slice_by(const std::wstring & str, wchar_t c, std::vector & out); +bool is_in_list(const std::wstring & item, const std::vector & list); +bool is_in_list(const std::wstring & item, const std::list & list); +bool is_in_list(const std::wstring & item, const std::set & list); + + + +template +bool is_in_list_generic(const std::wstring & item, const ContainerType & list) +{ + for(const std::wstring & str : list) + { + if( str == item ) + { + return true; + } + } + + return false; +} + + + } // namespace Winix diff --git a/winixd/core/request.cpp b/winixd/core/request.cpp index e2e74cc..390d964 100644 --- a/winixd/core/request.cpp +++ b/winixd/core/request.cpp @@ -1536,10 +1536,25 @@ void Request::PrepareSessionCookie() } +void Request::CheckOriginHeader() +{ + pt::Space * origin = headers_in.get_space_nc(L"Origin"); + + if( origin && origin->is_wstr() && !out_headers.has_key(Header::access_control_allow_origin) ) + { + if( function && function->IsCorsOriginAvailable(*origin->get_wstr()) ) + { + function->AddAccessControlAllowOriginHeader(*origin->get_wstr()); + } + } +} + + void Request::PrepareHeaders(bool compressing, int compress_encoding, size_t output_size) { PrepareSessionCookie(); + CheckOriginHeader(); if( send_as_attachment ) { diff --git a/winixd/core/request.h b/winixd/core/request.h index 6eda69a..d246ca2 100644 --- a/winixd/core/request.h +++ b/winixd/core/request.h @@ -595,6 +595,7 @@ private: int SelectDeflateVersion(); void SelectCompression(size_t source_len, bool & compression_allowed, int & compression_encoding); void PrepareSessionCookie(); + void CheckOriginHeader(); void PrepareHeaders(bool compressing, int compress_encoding, size_t output_size); void ModifyStatusForRedirect(); void PrepareSendFileHeaderForStaticMountpoint(); diff --git a/winixd/functions/functionbase.cpp b/winixd/functions/functionbase.cpp index d154aab..975c4bc 100644 --- a/winixd/functions/functionbase.cpp +++ b/winixd/functions/functionbase.cpp @@ -151,8 +151,22 @@ bool FunctionBase::IsCorsMethodAvailable(Request::Method method) bool FunctionBase::IsCorsOriginAvailable(const std::wstring & origin_url) { - // true by default for all urles - return true; + if( config ) + { + if( config->allow_all_cors_origins ) + { + return true; + } + else + { + // origin_url can be a "null" string + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin + // but in such a case the "null" should be put to the config as well + return is_in_list(origin_url, config->allowed_cors_origins); + } + } + + return false; } @@ -175,10 +189,25 @@ void FunctionBase::AddAccessControlAllowMethodsHeader(Request::Method method) /* * origin_url is the value of Origin header sent by the client + * origin_url can be: "null" + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin + * */ void FunctionBase::AddAccessControlAllowOriginHeader(const std::wstring & origin_url) { - cur->request->AddHeader(Header::access_control_allow_origin, origin_url); + if( config ) + { + if( config->allow_all_cors_origins ) + { + cur->request->AddHeader(Header::access_control_allow_origin, L"*"); + } + else + { + // method IsCorsOriginAvailable(..) was called beforehand so now we assume + // that the origin_url is permitted + cur->request->AddHeader(Header::access_control_allow_origin, origin_url); + } + } } @@ -246,27 +275,34 @@ void FunctionBase::MakeOptions() * (we allow Access-Control-Request-Headers not to be present) */ Request::Method method = Request::CheckRequestMethod(cors_method->get_wstr()->c_str()); + bool cors_available = false; if( IsCorsMethodAvailable(method) && IsCorsOriginAvailable(*cors_origin->get_wstr()) ) { - bool cors_available = true; + cors_available = true; if( cors_headers && cors_headers->is_wstr() ) { cors_available = AreCorsHeadersAvailable(*cors_headers->get_wstr()); } + } - if( cors_available ) + if( cors_available ) + { + AddAccessControlAllowMethodsHeader(method); + AddAccessControlAllowOriginHeader(*cors_origin->get_wstr()); + AddAccessControlMaxAgeHeader(); + + if( cors_headers && cors_headers->is_wstr() ) { - AddAccessControlAllowMethodsHeader(method); - AddAccessControlAllowOriginHeader(*cors_origin->get_wstr()); - AddAccessControlMaxAgeHeader(); - - if( cors_headers && cors_headers->is_wstr() ) - { - AddAccessControlAllowHeadersHeader(*cors_headers->get_wstr()); - } + AddAccessControlAllowHeadersHeader(*cors_headers->get_wstr()); } + + log << log3 << "FunctionBase: cors requests are permitted" << logend; + } + else + { + log << log2 << "FunctionBase: cors requests are not permitted" << logend; } } else