/* * This file is a part of Winix * and is distributed under the 2-Clause BSD licence. * Author: Tomasz Sowa */ /* * Copyright (c) 2008-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. * */ #ifndef headerfile_winix_core_request #define headerfile_winix_core_request #include #include #include #include "requesttypes.h" #include "models/item.h" #include "error.h" #include "config.h" #include "textstream.h" #include "templates/htmltextstream.h" #include "date/date.h" #include "space/space.h" #include "textstream/textstream.h" #include "outstreams.h" #include "models.h" #include "models/winixmodel.h" #include "header.h" #include "compress.h" #include "plugin.h" #include "mount.h" #include "mounts.h" #include "jobtask.h" namespace Winix { class FunctionBase; class Templates; class Request : public WinixModel { public: // how many input headers can be put to in_headers struct static const size_t MAX_INPUT_HEADERS = 32; // how many characters there can be in one header name static const size_t INPUT_HEADER_NAME_MAX_LENGTH = 64; // how many characters there can be in one header value static const size_t INPUT_HEADER_VALUE_MAX_LENGTH = 8192; /* request id is incremented for each request and is never 0 (from -1 will be incremented to one) it's used for some optimizations e.g. in templates */ size_t id; // the state of the request // not_assigned - the object is not being used // normal_run - run in the main thread, this state is set after a new request is made // assigned_to_job - the request is preserved and a new job will be called // job_run - run in the job thread (objects are locked) // finished - the request is finished and the object can be removed enum RunState { not_assigned = 0, normal_run, assigned_to_job, job_run, job_continuation_run, prepare_to_finish, finished}; RunState run_state; /* * request start time * * start_time is the same as timespec_req_start.tv_sec * start_date is a pt::Date converted from start_time * */ timespec timespec_req_start; time_t start_time; pt::Date start_date; /* * request stop time * */ timespec timespec_req_stop; /* * request stop time - start time * */ timespec timespec_req_diff; /* * start time of the ezc engine (html templates) * */ timespec timespec_ezc_engine_start; /* * end time of the ezc engine (html templates) * */ timespec timespec_ezc_engine_stop; /* * * * * variables representing input from client's browser * * * */ /* the HTTP method !! IMPROVE ME add the rest methods here */ enum Method { get, head, post, put, delete_, connect, options, trace, patch, unknown_method } method; /* subdomain subdomain = HTTP_HOST environment variable - config->base_url */ std::wstring subdomain; /* raw parameters !! CHECK ME may post_tab and cookie_tab should be changed to pt::Space now? or may change the name to cookie_in? or in_cookie? */ PostFileTab post_file_tab; CookieTab cookie_tab; pt::Space post_in; // input headers (without cookies) // at the moment we are using FastCGI and HTTP headers are prefixed with 'HTTP_' string // so we drop the prefix and change all characters to small ones // although https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 says that there can be more // than one http header with the same name we do not support it // each header has a different name here, cookies we have in a different container (cookie_tab) pt::Space headers_in; /* html anchor (those part of URI after '#' character) */ std::wstring anchor; // environment variables std::wstring env_request_method; std::wstring env_request_uri; std::wstring env_http_cookie; std::wstring env_http_host; std::wstring env_http_user_agent; std::wstring env_http_accept_encoding; std::wstring env_http_accept; std::wstring env_http_accept_language; std::wstring env_fcgi_role; std::wstring env_content_type; std::wstring env_https; // current IP address of the remote host // (read either from REMOTE_ADDR environment variable or from config.proxy_ip_header HTTP variable if config.check_proxy_ip_header is set to true) // (at the moment only IPv4 are supported) int ip; std::wstring ip_str; // ip_str can be ipv6 now // true if the browser is Microsoft Internet Explorer bool browser_msie; // true if the browser is Konqueror bool browser_konqueror; // true if we are using an encrypted connection (SSL) bool using_ssl; // true if the request is being made by ajax by htmx library bool is_htmx_request; /* request input variables representing the winix filesystem */ // current directory std::vector dir_tab; // true if a file exists bool is_item; // current file (valid if is_item is true) Item item; // current winix function // null if there is no a function FunctionBase * function; // current session (if exists, can be null) Session * session; // current mount point (can be null, it is treated as cms filesystem then) Mount * mount; // parameters (name:value) ParamTab param_tab; // this is a pointer either to the item (if exists) or to the last directory Item * last_item; // can we use gzip compression algorithm when sending content to the client bool accept_gzip; // can we use deflate compression algorithm when sending content to the client bool accept_deflate; /* * * * * variables for generating output to the client's browser * * * */ // // the algorithm how a request's container is selected is shown below: // (the whole answer's algorightm is implemented in PrepareAnswerType() method) // ------------------------------------------------------------------------------------------ // // at the beginning we set container_type to "raw" meaning simple text or html, then // we check the "Accept" http header, if it is set then we set container_type accordingly: // // Accept | container_type // ----------------------------------------------- // text/html | container_raw // application/xhtml+xml | container_raw // application/json | container_json // application/xml | container_xml // text/csv | container_csv // // next we check "container" url parameter, if it is set then we set container_type accordingly // ("container" url parameter has higher precedence than "Accept" http header): // // container | container_type // ----------------------------------------------- // raw | container_raw // json | container_json // xml | container_xml // csv | container_csv // // // Samples: // // http://domain.tld/dir/controller // returns html answer from the main ezc stream // // http://domain.tld/dir/controller/container:raw // returns html answer from the main ezc stream (the same as above) // // http://domain.tld/dir/controller/frame:abc // returns "abc" frame as html // // http://domain.tld/dir/controller/container:json // returns all serialized models to json and no ezc streams // // http://domain.tld/dir/controller/container:xml // returns all serialized models to xml and no ezc streams // // http://domain.tld/dir/controller/container:json/frame:abc,xyz // returns all serialized models to json and two frames in 'ezc_frames' object // // http://domain.tld/dir/controller/container:json/all_frames // returns all serialized models to json and all frames in 'ezc_frames' object // // http://domain.tld/dir/controller/container:json/main_stream // returns all serialized models and the main ezc stream in 'main_stream' field // // http://domain.tld/dir/controller/container:json/main_stream/all_frames // returns all serialized models to json, all frames and the main stream // bool send_bin_stream; bool send_main_stream; bool send_all_frames; std::vector send_frames; bool use_ezc_engine; bool serialize_models; // change maybe answer_text -> container_text? enum ContainerType { container_raw, container_json, container_xml, container_csv, }; ContainerType container_type; // at the beginning those with higher priority std::vector accept_mime_types; // at the beginning those with higher priority std::vector accept_languages; // request status // DEPRECATED, use http_status instead Error status; /* * HTTP result status * at the moment default is -1 which means it is not used (use status in such a case) */ int http_status; // if not empty means an address for redirecting to // it should be url-encoded std::wstring redirect_to; // a redirect type // following redirect types are supported: // 300 Multiple Choices // 301 Moved Permanently // 302 Found // 303 See Other (default) // 307 Temporary Redirect int redirect_type; // send header X-LIGHTTPD-send-file with path to a file std::wstring x_sendfile; // send as attachment (causes generating header: content-disposition: attachment) bool send_as_attachment; // headers send to the client (without cookies) (may change to headers_out?) pt::Space out_headers; // cookies send to the client // a value can be either a cookie value or the whole cookie string (with domain, date etc) pt::Space out_cookies; // binary page sent to the client if answer_source is answer_bin_stream BinaryPage out_bin_stream; // main text output stream where the html otput is generated from ezc templates // here the whole html page (with doctype, head, body) is generated HtmlTextStream out_main_stream; // text output streams used in ajax requests // in ezc templates you can use [ezc frame "stream_name"] or just [frame "stream_name"] keyword // to switch between streams Ezc::OutStreams out_streams; // models to return or to render through ezc library Ezc::Models models; // filter html content with HTMLFilter, default the same as config.html_filter bool use_html_filter; // if this variable is true then winix always return 200 OK header // when the status would be 404 (not found) or 403 (permission denied) // default: false bool use_200_status_for_not_found_and_permission_denied; // options used by ezc generators bool gen_trim_white; bool gen_skip_new_line; bool gen_use_special_chars; // index template name std::wstring html_template; /* additional variables used for common uses */ // DEPRECATED will be removed // usually items in the current directory (depends on the function) std::vector item_tab; /* * FastCGI request structure with pointers to input/output streams */ FCGX_Request fcgi_request; long job_id; // the main id of your job long job_second_id; // a secondary id of your job pt::Space job; Request(); void SetConfig(Config * config); void SetTemplates(Templates * templates); void SetCompress(Compress * compress); void SetPlugin(Plugin * plugin); void SetMounts(Mounts * mounts); void fields(); void RequestStarts(); void RequestEnds(); void Clear(); void PrepareAnswerType(); bool IsParam(const wchar_t * param_name); bool IsParam(const std::wstring & param_name); const std::wstring & ParamValue(const wchar_t * param_name); // returns an empty string if there is no such a parameter const std::wstring & ParamValue(const std::wstring & param_name); // returns an empty string if there is no such a parameter std::wstring * ParamValuep(const wchar_t * param_name); // returns nullptr if there is no such a parameter std::wstring * ParamValuep(const std::wstring & param_name); // returns nullptr if there is no such a parameter void AddParam(const std::wstring & param_name, const std::wstring & param_value); void AddParam(const wchar_t * param_name, const wchar_t * param_value); void RemoveParam(const wchar_t * param_name); void RemoveParam(const std::wstring & param_name); pt::Space * AddPostVar(pt::Space & space, const wchar_t * name); bool AddPostVar(const wchar_t * name, const wchar_t * value); bool AddPostVar(const std::wstring & name, const std::wstring & value); bool IsPostVar(const wchar_t * var); bool IsPostVar(const std::wstring & var); const std::wstring & PostVar(const wchar_t * var); // returns an empty string if there is no such a parameter const std::wstring & PostVar(const std::wstring & var); // returns an empty string if there is no such a parameter bool PostVar(const wchar_t * var, std::wstring & result); bool PostVar(const std::wstring & var, std::wstring & result); std::wstring * PostVarp(const wchar_t * var); std::wstring * PostVarp(const std::wstring & var); void AddCookie( const std::wstring & name, const std::wstring * value_string = nullptr, const pt::Stream * value_stream = nullptr, pt::Date * expires = nullptr, const std::wstring * path = nullptr, const std::wstring * domain = nullptr, CookieSameSite cookie_same_site = CookieSameSite::samesite_notset, bool http_only = false, bool secure = false); void AddDefaultSessionCookie(const std::wstring & value, pt::Date * expires = nullptr); bool has_frame(const wchar_t * frame); bool has_frame(const std::wstring & frame); void create_job(long job_id = JobTask::JOB_ID_DEFAULT, long job_secondary_id = JobTask::JOB_ID_DEFAULT); void modify_status_code_if_needed(); // RENAMEME to add_header_if_not_exists bool AddHeader(const wchar_t * name, const wchar_t * value); bool AddHeader(const wchar_t * name, long value); bool AddHeader(const std::wstring & name, const std::wstring & value); bool AddHeader(const wchar_t * name, const pt::WTextStream & value); bool AddHeader(const std::wstring & name, const pt::WTextStream & value); void FinishRequest(); void SetEnv(const char * name, std::wstring & env); void ReadEnvVariables(); void ReadEnvRemoteIP(); static Method CheckRequestMethod(const wchar_t * name); void CheckRequestMethod(); void CheckSSL(); void SetSubdomain(); static void PutMethodName(Request::Method method, pt::Stream & stream); void PutMethodName(pt::Stream & stream); private: Config * config; Templates * templates; Compress * compress; Plugin * plugin; Mounts * mounts; BinaryPage output_8bit; BinaryPage compressed_output; pt::WTextStream output_tmp_filtered_stream; pt::TextStream serialized_model; std::string aheader_name, aheader_value; std::wstring cookie_id_string; std::string send_data_buf; std::wstring http_header_name; std::string http_header_8bit; // used in ParamValue() and PostVar() when there is no such a param const std::wstring str_empty; void ClearOutputStreams(); void CheckAcceptHeader(); void CheckContainerParameter(); void PrepareFrameNames(); void current_dir(morm::Wrapper & wrapper); void last_item_wrapper(morm::Wrapper & wrapper); void http_status_error_title(EzcEnv & env); void http_status_error_description(EzcEnv & env); void PrepareAndSendAnswer(); void PrepareRawAnswer(); void PrepareJsonAnswer(); void PrepareXmlAnswer(); void PrepareCsvAnswer(); void PrepareContenerizedAnswer(); void PutSeparatorIfNeeded(bool put_separator); void SerializeFieldJson(const wchar_t * field_name); void SerializeStream(const pt::WTextStream & input_stream, const wchar_t * field_name); void SerializeStreamJson(const pt::WTextStream & input_stream, const wchar_t * field_name); void SerializeStreamXml(const pt::WTextStream & input_stream, const wchar_t * field_name); void SerializeStreamCsv(const pt::WTextStream & input_stream, const wchar_t * field_name); void SerializeAllFrames(); void SerializeSpecificFrames(); void SerializeModels(); void SerializeModel(morm::Wrapper & wrapper, const wchar_t * field_name); void SerializeModelJson(morm::Wrapper & wrapper, const wchar_t * field_name); void SerializeModelXml(morm::Wrapper & wrapper, const wchar_t * field_name); void SerializeModelCsv(morm::Wrapper & wrapper, const wchar_t * field_name); void FilterHtmlIfNeeded(const pt::WTextStream & input_stream, BinaryPage & output, bool clear_stream = true); void Send8bitOutput(BinaryPage & output); void SendData(const BinaryPage & page, FCGX_Stream * out); void UseEzcGenerator(); int SelectDeflateVersion(); void SelectCompression(size_t source_len, bool & compression_allowed, int & compression_encoding); void PrepareSessionCookie(); void PrepareHeaders(bool compressing, int compress_encoding, size_t output_size); void ModifyStatusForRedirect(); void PrepareSendFileHeaderForStaticMountpoint(); void PrepareSendFileHeader(); void PrepareContentEncodingHeader(int compress_encoding); void PrepareContentLengthHeader(size_t output_size); void PrepareHeaderContentType(); void PrepareHeaderStatus(int http_status); void SendHeaders(); void SendCookies(); bool CreateStaticResourcePath(pt::WTextStream & out_path); bool CanSendContent(); void LogRequestTime(); MORM_MEMBER_FIELD(Request) }; } // namespace Winix #endif