winix/winixd/core/request.h

531 lines
14 KiB
C++

/*
* This file is a part of Winix
* and is distributed under the 2-Clause BSD licence.
* Author: Tomasz Sowa <t.sowa@ttmath.org>
*/
/*
* Copyright (c) 2008-2021, 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 <fcgiapp.h>
#include <sstream>
#include <vector>
#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"
namespace Winix
{
class FunctionBase;
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;
/*
* 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, post, head, delete_, 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?
*/
PostTab post_tab;
PostFileTab post_file_tab;
CookieTab cookie_tab;
pt::Space post_in;
bool is_postin_used;// temporarily, before all post variables will be put to 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_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<Item*> 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;
// parameters (name:value)
ParamTab param_tab;
// this is a pointer either to the item (if exists) or to the last directory
Item * last_item;
/*
*
*
*
* 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<std::wstring> 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<HeaderValue> accept_mime_types;
// request status
// !! CHANGE ME it'll be better to use ordinary http result codes
Error 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<HtmlTextStream, true> 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> item_tab;
Request();
void SetConfig(Config * pconfig);
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
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);
bool AllPostVarEmpty(); // returning true if all post vars are empty
// setting a cookie
// name - cookie name (either const wchar_t, or std::wstring or pt::WTextStream)
// value - cookie value (can be everything which can be put to pt::WTextStream stream)
// the return std::wstring reference is a reference to the cookie inserted value (in out_cookies structure)
template<typename NameType, typename ValueType>
void AddCookie(const NameType & name, const ValueType & value, pt::Date * expires = 0);
template<typename NameType, typename ValueType>
void AddCookie(const NameType & name, const ValueType & value, pt::Date & expires);
bool has_frame(const wchar_t * frame);
bool has_frame(const std::wstring & frame);
private:
Config * config;
// 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);
MORM_MEMBER_FIELD(Request)
};
template<typename NameType, typename ValueType>
void Request::AddCookie(const NameType & name, const ValueType & value, pt::Date * expires)
{
pt::WTextStream cookie;
cookie << value;
if( cookie.empty() )
cookie << L"\"\""; // cookie empty value
if( expires )
cookie << L"; expires=" << DateToStrCookie(*expires) << L" GMT";
cookie << L"; path=/; domain=" << config->base_url;
/*
!! IMPROVE ME add an option to the config
don't use '; secure' flag if you are using both sites (with SSL
and without SSL) -- with secure flag the cookie is sent only through
SSL and if you accidentally open a new window without SSL (http://)
then winix will create a new session for you and the previous session (https://)
will be lost (the session cookie will be overwritten in the client's browser)
*/
out_cookies.add_stream(name, cookie);
}
template<typename NameType, typename ValueType>
void Request::AddCookie(const NameType & name, const ValueType & value, pt::Date & expires)
{
AddCookie(name, value, &expires);
}
} // namespace Winix
#endif