/* * This file is a part of Winix * and is distributed under the 2-Clause BSD licence. * Author: Tomasz Sowa */ /* * Copyright (c) 2010-2022, Tomasz Sowa * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include "functionbase.h" #include "functions.h" #include "templates/templates.h" namespace Winix { FunctionBase::FunctionBase() { follow_symlinks = true; template_index = size_t(-1); need_ssl = false; need_session = true; register_default_models = true; post_max_object_items = 0; post_max_table_items = 0; post_max_all_items = 0; post_max_nested_objects = 0; fun.item_content.user_id = -1; fun.item_content.group_id = -1; fun.item_content.privileges = 07555; fun.parent_id = -1; fun.id = -1; fun.type = Item::file; db = nullptr; functions = nullptr; templates = nullptr; } FunctionBase::~FunctionBase() { } //void FunctionBase::SetConfig(Config * pconfig) //{ // config = pconfig; //} //void FunctionBase::SetCur(Cur * pcur) //{ // cur = pcur; //} void FunctionBase::SetDb(Db * pdb) { db = pdb; } //void FunctionBase::SetSystem(System * psystem) //{ // system = psystem; //} void FunctionBase::SetFunctions(Functions * pfunctions) { functions = pfunctions; } void FunctionBase::SetTemplates(Templates * ptemplates) { templates = ptemplates; } //void FunctionBase::SetSynchro(Synchro * psynchro) //{ // synchro = psynchro; //} //void FunctionBase::SetSessionManager(SessionManager * pmanager) //{ // session_manager = pmanager; //} void FunctionBase::Init() { // this method is called only once at the beginning // when winix starts } void FunctionBase::Finish() { // this method is called only once at the end // when winix finishes } bool FunctionBase::HasAccess() { // true by default return true; } /* * this is in a response to the normal OPTIONS method (not cors request) */ void FunctionBase::AddAllowMethodsHeader() { cur->request->out_headers.add(Header::allow, L"GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH"); } bool FunctionBase::IsCorsMethodAvailable(Request::Method method) { return method == Request::get || method == Request::head || method == Request::post || method == Request::put || method == Request::delete_ ||method == Request::patch; } bool FunctionBase::IsOriginAvailable(const std::wstring & origin_url) { if( config ) { if( !config->allowed_origins.empty() ) { // 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_origins); } else { if( config->allow_all_origins ) { return true; } } } return false; } bool FunctionBase::AreCorsCredentialsAvailable() { return config && config->access_control_allow_credentials; } bool FunctionBase::AreCorsHeadersAvailable(const std::wstring & headers) { // true by default for all headers // headers are comma separated return true; } /* * method is the value of Access-Control-Request-Method header sent by the client */ void FunctionBase::AddAccessControlAllowMethodsHeader(Request::Method method) { cur->request->AddHeader(Header::access_control_allow_methods, L"GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH"); } /* * 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) { if( config ) { if( !config->allowed_origins.empty() ) { // method IsOriginAvailable(..) was called beforehand so now we assume // that the origin_url is permitted (and is valid as a header) cur->request->AddHeader(Header::access_control_allow_origin, origin_url); } else { if( config->allow_all_origins ) { cur->request->AddHeader(Header::access_control_allow_origin, L"*"); } } } } /* * headers is the value of Access-Control-Request-Headers header sent by the client */ void FunctionBase::AddAccessControlAllowHeadersHeader(const std::wstring & headers) { if( Header::is_header_value_correct(headers) ) { cur->request->AddHeader(Header::access_control_allow_headers, headers); } } void FunctionBase::AddAccessControlMaxAgeHeader() { // default 24 hours cur->request->AddHeader(Header::access_control_max_age, 86400); } void FunctionBase::AddAccessControlAllowCredentialsHeader() { cur->request->AddHeader(Header::access_control_allow_credentials, L"true"); } void FunctionBase::AddAccessControlExposeHeadersHeader() { if( config ) { if( !config->access_control_expose_headers.empty() ) { pt::WTextStream headers; bool is_first = true; for(std::wstring & str : config->access_control_expose_headers) { if( !is_first ) headers << ", "; headers << str; is_first = false; } cur->request->AddHeader(Header::access_control_expose_headers, headers); } } } void FunctionBase::AddCorsPreflightRequestHeaders(const std::wstring & origin, Request::Method method, const std::wstring * request_headers) { AddAccessControlAllowMethodsHeader(method); AddAccessControlAllowOriginHeader(origin); AddAccessControlMaxAgeHeader(); AddAccessControlExposeHeadersHeader(); if( AreCorsCredentialsAvailable() ) { AddAccessControlAllowCredentialsHeader(); } if( request_headers ) { AddAccessControlAllowHeadersHeader(*request_headers); } log << log3 << "FunctionBase: this cors request is permitted" << logend; } void FunctionBase::AddCorsNormalRequestHeaders(const std::wstring & origin) { AddAccessControlAllowOriginHeader(origin); if( AreCorsCredentialsAvailable() ) { AddAccessControlAllowCredentialsHeader(); } } void FunctionBase::CheckCorsPreflightRequest(const std::wstring & origin, const std::wstring & method_string) { pt::Space * cors_headers = cur->request->headers_in.get_space_nc(L"Access_Control_Request_Headers"); Request::Method method = Request::CheckRequestMethod(method_string.c_str()); if( IsCorsMethodAvailable(method) ) { bool cors_headers_available = true; std::wstring * headers = nullptr; if( cors_headers && cors_headers->is_wstr() ) { headers = cors_headers->get_wstr(); cors_headers_available = AreCorsHeadersAvailable(*headers); } if( cors_headers_available ) { AddCorsPreflightRequestHeaders(origin, method, headers); } else { if( headers ) { log << log2 << "FunctionBase: these cors headers: " << *headers << " are not permitted in cors requests" << logend; } } } else { log << log2 << "FunctionBase: this method: " << method_string << " is not permitted in cors requests" << logend; } } void FunctionBase::AddResponseHeadersForOrigin(const std::wstring & origin) { if( cur->request->method == Request::Method::options ) { pt::Space * cors_method = cur->request->headers_in.get_space_nc(L"Access_Control_Request_Method"); // FastCGI changes '-' to '_' cur->request->http_status = Header::status_204_no_content; if( cors_method && cors_method->is_wstr() ) { /* * this is a preflight request * https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request * (we allow Access-Control-Request-Headers not to be present) */ if( config->are_cors_preflight_requests_available ) { CheckCorsPreflightRequest(origin, *cors_method->get_wstr()); } else { log << log2 << "FunctionBase: cors requests are disabled" << logend; } } else { /* * this is not a preflight cors request */ AddAllowMethodsHeader(); AddCorsNormalRequestHeaders(origin); } } else { AddCorsNormalRequestHeaders(origin); } } void FunctionBase::CheckOriginHeader() { pt::Space * origin = cur->request->headers_in.get_space_nc(L"Origin"); if( origin && origin->is_wstr() ) { if( IsOriginAvailable(*origin->get_wstr()) ) { AddResponseHeadersForOrigin(*origin->get_wstr()); } else { cur->request->http_status = Header::status_204_no_content; log << log2 << "FunctionBase: this origin: " << *origin->get_wstr() << " is not permitted for cors requests" << logend; } /* * https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties * https://security.stackexchange.com/questions/151590/vary-origin-response-header-and-cors-exploitation * It's important to include the Vary: Origin header to prevent caching. The header indicates that the response * is in some way dependent on the origin and should therefore not be served from cache for any other origin. * If the header is missing, cache poisoning attacks might be possible */ cur->request->AddHeader(Header::very, Header::origin); } else { if( cur->request->method == Request::Method::options ) { AddAllowMethodsHeader(); } } } void FunctionBase::MakeGet() { // do nothing by default } void FunctionBase::MakeHead() { // by default call MakeGet() but we do not return any content at the end of the request MakeGet(); } void FunctionBase::MakePost() { // do nothing by default } void FunctionBase::MakePut() { // do nothing by default } void FunctionBase::MakeDelete() { // do nothing by default } void FunctionBase::MakeConnect() { // do nothing by default } void FunctionBase::MakeOptions() { // do nothing by default } void FunctionBase::MakeTrace() { // do nothing by default } void FunctionBase::MakePatch() { // do nothing by default } void FunctionBase::Clear() { // do nothing by default } void FunctionBase::ContinueMakeGet() { // do nothing by default } void FunctionBase::ContinueMakeHead() { // do nothing by default } void FunctionBase::ContinueMakePost() { // do nothing by default } void FunctionBase::ContinueMakePut() { // do nothing by default } void FunctionBase::ContinueMakeDelete() { // do nothing by default } void FunctionBase::ContinueMakeConnect() { // do nothing by default } void FunctionBase::ContinueMakeOptions() { // do nothing by default } void FunctionBase::ContinueMakeTrace() { // do nothing by default } void FunctionBase::ContinueMakePatch() { // do nothing by default } } // namespace Winix