/* * This file is a part of Winix * and is distributed under the 2-Clause BSD licence. * Author: Tomasz Sowa */ /* * Copyright (c) 2010-2024, 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 "core/misc.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; functions = nullptr; templates = nullptr; } FunctionBase::~FunctionBase() { } //void FunctionBase::SetConfig(Config * pconfig) //{ // config = pconfig; //} //void FunctionBase::SetCur(Cur * pcur) //{ // cur = pcur; //} //void FunctionBase::SetSystem(System * psystem) //{ // system = psystem; //} void FunctionBase::set_functions(Functions * pfunctions) { functions = pfunctions; } void FunctionBase::set_templates(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 Init(); } void FunctionBase::finish() { // this method is called only once at the end // when winix finishes Finish(); } /* * this is in a response to the normal OPTIONS method (not cors request) */ void FunctionBase::add_allow_methods_header() { cur->request->out_headers.add(Header::allow, L"GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH"); } bool FunctionBase::is_cors_method_available(Request::Method method) { return method == Request::get || method == Request::head || method == Request::post || method == Request::put || method == Request::delete_ ||method == Request::patch; } bool FunctionBase::is_origin_available(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::are_cors_credentials_available() { return config && config->access_control_allow_credentials; } bool FunctionBase::are_cors_headers_available(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::add_access_control_allow_methods_header(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::add_access_control_allow_origin_header(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::add_access_control_allow_headers_header(const std::wstring & headers) { if( Header::is_header_value_correct(headers) ) { cur->request->AddHeader(Header::access_control_allow_headers, headers); } } void FunctionBase::add_access_control_max_age_header() { // default 24 hours cur->request->AddHeader(Header::access_control_max_age, 86400); } void FunctionBase::add_access_control_allow_credentials_header() { cur->request->AddHeader(Header::access_control_allow_credentials, L"true"); } void FunctionBase::add_access_control_expose_headers_header() { 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::add_cors_preflight_request_headers(const std::wstring & origin, Request::Method method, const std::wstring * request_headers) { add_access_control_allow_methods_header(method); add_access_control_allow_origin_header(origin); add_access_control_max_age_header(); add_access_control_expose_headers_header(); if( are_cors_credentials_available() ) { add_access_control_allow_credentials_header(); } if( request_headers ) { add_access_control_allow_headers_header(*request_headers); } log << log3 << "FunctionBase: this cors request is permitted" << logend; } void FunctionBase::add_cors_normal_request_headers(const std::wstring & origin) { add_access_control_allow_origin_header(origin); add_access_control_expose_headers_header(); if( are_cors_credentials_available() ) { add_access_control_allow_credentials_header(); } } void FunctionBase::check_cors_preflight_request(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( is_cors_method_available(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 = are_cors_headers_available(*headers); } if( cors_headers_available ) { add_cors_preflight_request_headers(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::add_response_headers_for_origin(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 ) { check_cors_preflight_request(origin, *cors_method->get_wstr()); } else { log << log2 << "FunctionBase: cors requests are disabled" << logend; } } else { /* * this is not a preflight cors request */ add_allow_methods_header(); add_cors_normal_request_headers(origin); } } else { add_cors_normal_request_headers(origin); } } void FunctionBase::check_origin_header() { pt::Space * origin = cur->request->headers_in.get_space_nc(L"Origin"); if( origin && origin->is_wstr() ) { if( is_origin_available(*origin->get_wstr()) ) { add_response_headers_for_origin(*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 ) { add_allow_methods_header(); } } } bool FunctionBase::has_access() { // true by default //return true; return HasAccess(); /* for backward compatibility, will be removed */ } bool FunctionBase::has_get_access() { return has_access(); } bool FunctionBase::has_head_access() { return has_access(); } bool FunctionBase::has_post_access() { return has_access(); } bool FunctionBase::has_put_access() { return has_access(); } bool FunctionBase::has_delete_access() { return has_access(); } bool FunctionBase::has_connect_access() { return has_access(); } bool FunctionBase::has_options_access() { return has_access(); } bool FunctionBase::has_trace_access() { return has_access(); } bool FunctionBase::has_patch_access() { return has_access(); } void FunctionBase::start_request() { // do nothing by default } void FunctionBase::make_get() { // do nothing by default MakeGet(); } void FunctionBase::make_head() { // by default call MakeGet() but we do not return any content at the end of the request MakeHead(); } void FunctionBase::make_post() { // do nothing by default MakePost(); } void FunctionBase::make_put() { // do nothing by default MakePut(); } void FunctionBase::make_delete() { // do nothing by default MakeDelete(); } void FunctionBase::make_connect() { // do nothing by default MakeConnect(); } void FunctionBase::make_options() { // do nothing by default MakeOptions(); } void FunctionBase::make_trace() { // do nothing by default MakeTrace(); } void FunctionBase::make_patch() { // do nothing by default MakePatch(); } void FunctionBase::finish_request() { // do nothing by default } void FunctionBase::clear() { winix_ezc_helper.set_connector(model_connector); winix_ezc_helper.clear(); // for backward compatibility - will be removed Clear(); } void FunctionBase::continue_make_get() { // do nothing by default ContinueMakeGet(); } void FunctionBase::continue_make_head() { // do nothing by default ContinueMakeHead(); } void FunctionBase::continue_make_post() { // do nothing by default ContinueMakePost(); } void FunctionBase::continue_make_put() { // do nothing by default ContinueMakePut(); } void FunctionBase::continue_make_delete() { // do nothing by default ContinueMakeDelete(); } void FunctionBase::continue_make_connect() { // do nothing by default ContinueMakeConnect(); } void FunctionBase::continue_make_options() { // do nothing by default ContinueMakeOptions(); } void FunctionBase::continue_make_trace() { // do nothing by default ContinueMakeTrace(); } void FunctionBase::continue_make_patch() { // do nothing by default ContinueMakePatch(); } bool FunctionBase::need_to_copy_raw_post() { return false; } bool FunctionBase::can_push_url_to_browser_history() { return true; } void FunctionBase::add_standard_models() { if( cur->request->use_ezc_engine ) { cur->request->models.Add(L"winix_ezc_helper", winix_ezc_helper); } } void FunctionBase::prepare_doc_url(const wchar_t * local_url, pt::WTextStream & url) { system->PutUrlProto(config->use_ssl, url, false); if( !cur->request->subdomain.empty() ) { url << cur->request->subdomain << '.'; } url << config->base_url; if( local_url ) { url << local_url; } } void FunctionBase::prepare_doc_url(const wchar_t * local_url, std::wstring & url) { pt::WTextStream stream; prepare_doc_url(local_url, stream); stream.to_str(url); } std::wstring FunctionBase::prepare_doc_url(const wchar_t * local_url) { std::wstring url; prepare_doc_url(local_url, url); return url; } std::wstring FunctionBase::prepare_doc_url(const std::wstring & local_url) { return prepare_doc_url(local_url.c_str()); } void FunctionBase::prepare_current_dir(const wchar_t * local_url, pt::WTextStream & url) { prepare_doc_url(nullptr, url); for(Item * dir : cur->request->dir_tab) { if( !dir->url.empty() ) url << L"/" << dir->url; } if( local_url ) { url << local_url; } } void FunctionBase::prepare_current_dir(const wchar_t * local_url, std::wstring & url) { pt::WTextStream stream; prepare_current_dir(local_url, stream); stream.to_str(url); } std::wstring FunctionBase::prepare_current_dir(const wchar_t * local_url) { std::wstring url; prepare_current_dir(local_url, url); return url; } std::wstring FunctionBase::prepare_current_dir(const std::wstring & local_url) { return prepare_current_dir(local_url.c_str()); } void FunctionBase::prepare_current_item(const wchar_t * local_url, pt::WTextStream & url) { prepare_current_dir(nullptr, url); if( cur->request->is_item ) url << L"/" << cur->request->item.url; if( local_url ) { url << local_url; } } void FunctionBase::prepare_current_item(const wchar_t * local_url, std::wstring & url) { pt::WTextStream stream; prepare_current_item(local_url, stream); stream.to_str(url); } std::wstring FunctionBase::prepare_current_item(const wchar_t * local_url) { std::wstring url; prepare_current_item(local_url, url); return url; } std::wstring FunctionBase::prepare_current_item(const std::wstring & local_url) { return prepare_current_item(local_url.c_str()); } void FunctionBase::prepare_current_function(const wchar_t * local_url, pt::WTextStream & url) { prepare_current_item(nullptr, url); url << L"/" << fun.url; if( local_url ) { url << local_url; } } void FunctionBase::prepare_current_function(const wchar_t * local_url, std::wstring & url) { pt::WTextStream stream; prepare_current_function(local_url, stream); stream.to_str(url); } std::wstring FunctionBase::prepare_current_function(const wchar_t * local_url) { std::wstring url; prepare_current_function(local_url, url); return url; } std::wstring FunctionBase::prepare_current_function(const std::wstring & local_url) { return prepare_current_function(local_url.c_str()); } void FunctionBase::redirect_to(const wchar_t * url, bool append_domain) { if( cur->request->is_htmx_request ) { if( append_domain ) { cur->request->out_headers.add(Header::hx_redirect, prepare_doc_url(url)); } else { cur->request->out_headers.add(Header::hx_redirect, url); } } else { if( cur->request->container_type == Request::ContainerType::container_raw ) { if( append_domain ) { prepare_doc_url(url, cur->request->redirect_to); } else { cur->request->redirect_to = url; } cur->request->redirect_type = Header::status_303_see_other; } } } void FunctionBase::redirect_to(const std::wstring & url, bool append_domain) { redirect_to(url.c_str(), append_domain); } void FunctionBase::redirect_to(const pt::WTextStream & url, bool append_domain) { std::wstring url_str; url.to_str(url_str); redirect_to(url_str, append_domain); } void FunctionBase::redirect_to(const wchar_t * url, const wchar_t * frame_url, const wchar_t * dom_target) { if( cur->request->is_htmx_request ) { /* * we do not use HX-Location because it will put the frame_url to the browser history * and there is no an option to disable it or change the url */ pt::WTextStream full_url, full_frame_url, hx_trigger_value; prepare_doc_url(url, full_url); prepare_doc_url(frame_url, full_frame_url); pt::Space & trigger = cur->request->out_headers.get_add_space(Header::hx_trigger); pt::Space & redirect = trigger.get_add_space(L"winix:redirect"); redirect.add(L"path", full_frame_url); redirect.add(L"target", dom_target); log << log3 << "FunctionBase: redirecting to: " << frame_url << ", dom_target: " << dom_target << logend; if( can_push_url_to_browser_history() ) { cur->request->out_headers.add(Header::hx_push_url, full_url); log << log3 << "FunctionBase: pushing a new url to the browser history: " << full_url << logend; } cur->request->out_headers.add(Header::hx_reswap, L"none"); } else { prepare_doc_url(url, cur->request->redirect_to); cur->request->redirect_type = Header::status_303_see_other; } } void FunctionBase::redirect_to(const std::wstring & url, const std::wstring & frame_url, const std::wstring & dom_target) { redirect_to(url.c_str(), frame_url.c_str(), dom_target.c_str()); } void FunctionBase::redirect_to(pt::WTextStream & url, pt::WTextStream & frame_url, pt::WTextStream & dom_target) { std::wstring url_str, frame_url_str, dom_target_str; url.to_str(url_str); frame_url.to_str(frame_url_str); dom_target.to_str(dom_target_str); redirect_to(url_str, frame_url_str, dom_target_str); } void FunctionBase::redirect_to(pt::WTextStream & url, pt::WTextStream & frame_url, const wchar_t * dom_target) { std::wstring url_str, frame_url_str; url.to_str(url_str); frame_url.to_str(frame_url_str); redirect_to(url_str.c_str(), frame_url_str.c_str(), dom_target); } void FunctionBase::redirect_to(const wchar_t * url, const wchar_t * frame_url, pt::WTextStream & dom_target) { std::wstring dom_target_str; dom_target.to_str(dom_target_str); redirect_to(url, frame_url, dom_target_str.c_str()); } void FunctionBase::retarged(const wchar_t * frame, const wchar_t * dom_target, const wchar_t * push_url, const wchar_t * swap_algorithm) { pt::WTextStream log_msg; if( frame && (*frame) ) { cur->request->send_frames.clear(); cur->request->send_frames.push_back(frame); log_msg << ", frame: " << frame; } if( dom_target && (*dom_target) ) { cur->request->out_headers.add(Header::hx_retarget, dom_target); log_msg << ", container: " << dom_target; } if( push_url && can_push_url_to_browser_history() ) { std::wstring url = prepare_doc_url(push_url); cur->request->out_headers.add(Header::hx_push_url, url); log << log3 << "FunctionBase: pushing a new url to the browser history: " << url << logend; } if( swap_algorithm && (*swap_algorithm) ) { cur->request->out_headers.add(Header::hx_reswap, swap_algorithm); } log << log3 << "FunctionBase: changing the targed" << log_msg << logend; } void FunctionBase::retarged(const std::wstring & frame, const std::wstring & dom_target, const std::wstring & push_url, const wchar_t * swap_algorithm) { retarged(frame.c_str(), dom_target.c_str(), push_url.c_str(), swap_algorithm); } void FunctionBase::retarged(const wchar_t * frame, pt::WTextStream & dom_target, const wchar_t * push_url, const wchar_t * swap_algorithm) { std::wstring dom_target_str; dom_target.to_str(dom_target_str); retarged(frame, dom_target_str.c_str(), push_url, swap_algorithm); } void FunctionBase::remove_content(pt::WTextStream & dom_target, bool close_dialogs) { pt::Space & trigger = cur->request->out_headers.get_add_space(Header::hx_trigger); pt::Space & remove_content = trigger.get_add_space(L"winix:removecontent"); pt::Space & rm = remove_content.get_add_space(L"rm"); rm.add(dom_target); if( close_dialogs ) { close_modal_dialogs(); } /* * CHECKME may we do not need to use the ezc engine at all? */ cur->request->send_frames.clear(); } void FunctionBase::remove_content(const wchar_t * dom_target, bool has_postfix, long dom_target_postfix, bool close_dialogs) { pt::WTextStream target; target << dom_target; if( has_postfix ) target << dom_target_postfix; remove_content(target, close_dialogs); } void FunctionBase::remove_content(const wchar_t * dom_target, long dom_target_postfix, bool close_dialogs) { remove_content(dom_target, true, dom_target_postfix, close_dialogs); } void FunctionBase::remove_content(const wchar_t * dom_target, bool close_dialogs) { remove_content(dom_target, false, 0, close_dialogs); } void FunctionBase::update_content(const wchar_t * frame, pt::WTextStream & dom_target, bool close_dialogs) { if( close_dialogs ) { close_modal_dialogs(); } retarged(frame, dom_target); } void FunctionBase::update_content(const wchar_t * frame, const wchar_t * dom_target, bool has_postfix, long dom_target_postfix, bool close_dialogs) { pt::WTextStream target; target << dom_target; if( has_postfix ) target << dom_target_postfix; update_content(frame, target, close_dialogs); } void FunctionBase::update_content(const wchar_t * frame, const wchar_t * dom_target, long dom_target_postfix, bool close_dialogs) { update_content(frame, dom_target, true, dom_target_postfix, close_dialogs); } void FunctionBase::update_content(const wchar_t * frame, const wchar_t * dom_target, bool close_dialogs) { update_content(frame, dom_target, false, 0, close_dialogs); } void FunctionBase::close_modal_dialogs() { if( cur->request->is_htmx_request ) { pt::Space & trigger = cur->request->out_headers.get_add_space(Header::hx_trigger); trigger.add(L"winix:closedialogs", true); } } void FunctionBase::push_url_to_current_dir(const wchar_t * local_url) { if( cur->request->is_htmx_request ) { pt::WTextStream url; prepare_current_dir(local_url, url); cur->request->out_headers.add(Header::hx_push_url, url); } } void FunctionBase::push_url_to_current_item(const wchar_t * local_url) { if( cur->request->is_htmx_request ) { pt::WTextStream url; prepare_current_item(local_url, url); cur->request->out_headers.add(Header::hx_push_url, url); } } void FunctionBase::push_url_to_current_function(const wchar_t * local_url) { if( cur->request->is_htmx_request ) { pt::WTextStream url; prepare_current_function(local_url, url); cur->request->out_headers.add(Header::hx_push_url, url); } } void FunctionBase::redirect_to_current_dir() { pt::WTextStream url; prepare_current_dir(nullptr, url); redirect_to(url, false); } void FunctionBase::redirect_to_current_item() { pt::WTextStream url; prepare_current_item(nullptr, url); redirect_to(url, false); } void FunctionBase::redirect_to_current_function() { pt::WTextStream url; prepare_current_function(nullptr, url); redirect_to(url, false); } void FunctionBase::redirect_if_needed(bool was_url_changed) { if( cur->request->container_type == Request::ContainerType::container_raw ) { /* save_and_close is not used at the moment anywhere in templates */ if( cur->request->post_in.has_key(L"save_and_close") ) { redirect_to_current_item(); } else if( was_url_changed ) { if( cur->request->is_htmx_request ) { push_url_to_current_function(); } else { system->RedirectToLastFunction(nullptr, false); } } } } /* * DEPRACATED * for backward compatibility */ void FunctionBase::Init() { } void FunctionBase::Finish() { } bool FunctionBase::HasAccess() { return true; } void FunctionBase::Clear() { } void FunctionBase::MakeGet() { } void FunctionBase::MakeHead() { make_get(); } void FunctionBase::MakePost() { } void FunctionBase::MakePut() { } void FunctionBase::MakeDelete() { } void FunctionBase::MakeConnect() { } void FunctionBase::MakeOptions() { } void FunctionBase::MakeTrace() { } void FunctionBase::MakePatch() { } void FunctionBase::ContinueMakeGet() { } void FunctionBase::ContinueMakeHead() { } void FunctionBase::ContinueMakePost() { } void FunctionBase::ContinueMakePut() { } void FunctionBase::ContinueMakeDelete() { } void FunctionBase::ContinueMakeConnect() { } void FunctionBase::ContinueMakeOptions() { } void FunctionBase::ContinueMakeTrace() { } void FunctionBase::ContinueMakePatch() { } } // namespace Winix