Browse Source

change the way how winix answer is created

Now we can return ezc content and models serialized in the same json structure,
Xml and Csv are not implemented yet.
Ezc frames are returned in 'ezc_frames' field.
Main ezc stream is returned in 'main_stream' field.
Frame url parameter can take more than one frame (names separated by commas).
Honor Accept http header (AcceptParser).

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 (not implemented yet)

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
master
Tomasz Sowa 6 months ago
parent
commit
f7b5ac0dc8
  1. 2
      winixd/Makefile.dep
  2. 4
      winixd/core/Makefile.dep
  3. 124
      winixd/core/acceptparser.h
  4. 377
      winixd/core/app.cpp
  5. 18
      winixd/core/app.h
  6. 11
      winixd/core/config.cpp
  7. 31
      winixd/core/config.h
  8. 49
      winixd/core/header.h
  9. 31
      winixd/core/misc.cpp
  10. 2
      winixd/core/misc.h
  11. 173
      winixd/core/request.cpp
  12. 216
      winixd/core/request.h
  13. 2
      winixd/functions/download.cpp
  14. 2
      winixd/functions/emacs.cpp
  15. 1
      winixd/main/Makefile.dep
  16. 2
      winixd/templates/Makefile.dep
  17. 9
      winixd/templates/winix.cpp

2
winixd/Makefile.dep

File diff suppressed because one or more lines are too long

4
winixd/core/Makefile.dep

@ -199,8 +199,8 @@ app.o: ../../../winix/winixd/templates/indexpatterns.h
app.o: ../../../winix/winixd/templates/patterns.h
app.o: ../../../winix/winixd/templates/changepatterns.h compress.h
app.o: postparser.h httpsimpleparser.h cookieparser.h postmultiparser.h
app.o: acceptencodingparser.h acceptbaseparser.h winixrequest.h
app.o: ../../../winix/winixd/models/migration.h
app.o: acceptencodingparser.h acceptbaseparser.h acceptparser.h
app.o: winixrequest.h ../../../winix/winixd/models/migration.h
basethread.o: basethread.h synchro.h winixmodeldeprecated.h
basethread.o: ../../../winix/winixd/core/winixbase.h
basethread.o: ../../../winix/winixd/core/config.h

124
winixd/core/acceptparser.h

@ -0,0 +1,124 @@
/*
* 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) 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_acceptparser
#define headerfile_winix_core_acceptparser
#include "acceptbaseparser.h"
#include "log.h"
#include "header.h"
namespace Winix
{
class AcceptParser : public AcceptBaseParser
{
public:
static constexpr size_t MAX_CONTAINER_LENGTH = 16;
/*
* IMPROVEME add support for something like "text/html;level=1" (skip the level part)
*
* https://developer.mozilla.org/en-US/docs/Glossary/Quality_values
* Some syntax, like the one of Accept, allow additional specifiers like text/html;level=1.
* These increase the specificity of the value. Their use is extremely rare.
*
*
*/
void Parse(const wchar_t * str, std::vector<HeaderValue> & header_values, bool clear_header_values = true)
{
if( clear_header_values )
header_values.clear();
this->header_values = &header_values;
AcceptBaseParser::Parse(str);
std::sort(header_values.begin(), header_values.end(), [](HeaderValue & h1, HeaderValue & h2) -> bool {
return h1.weight > h2.weight;
});
PutToLog(header_values);
}
void Parse(const std::wstring & str, std::vector<HeaderValue> & header_values, bool clear_header_values = true)
{
Parse(str.c_str(), header_values, clear_header_values);
}
private:
std::vector<HeaderValue> * header_values;
void Param(const std::wstring & param, double q)
{
if( header_values->size() < MAX_CONTAINER_LENGTH && q > 0.0 )
{
if( q > 1.0 )
q = 1.0;
header_values->resize(header_values->size() + 1);
header_values->back().value = param;
header_values->back().weight = q;
}
}
void PutToLog(std::vector<HeaderValue> & header_values)
{
if( !header_values.empty() )
{
log << log3 << "AP: " << Header::accept << " header consists of: ";
HeaderValue::log_values(header_values, log);
log << logend;
}
else
{
log << log3 << "AP: there is no " << Header::accept << " header" << logend;
}
}
};
} // namespace Winix
#endif

377
winixd/core/app.cpp

@ -386,6 +386,7 @@ bool App::Init()
cookie_parser.set_dependency(&winix_model);
accept_encoding_parser.set_dependency(&winix_base);
accept_parser.set_dependency(&winix_base);
plugin.Call((Session*)0, WINIX_PLUGIN_INIT);
@ -777,7 +778,7 @@ void App::SaveSessionsIfNeeded()
// !! IMPROVE ME change to a better name
void App::MakeEzcGenerator()
void App::UseEzcGenerator()
{
// if( cur.request->page_generated || !cur.request->redirect_to.empty() || !cur.request->x_sendfile.empty() )
// return;
@ -823,23 +824,19 @@ void App::CheckPostRedirect()
void App::AddDefaultModels()
{
// there is no need to add default models if we return a binary stream
if( cur.request->answer_source != Request::AnswerSource::answer_bin_stream )
if( cur.request->function && cur.request->function->register_default_models )
{
if( cur.request->function && cur.request->function->register_default_models )
{
// may it would be better do not return cur.request by default?
cur.request->models.Add(L"request", cur.request);
// may it would be better do not return cur.request by default?
cur.request->models.Add(L"request", cur.request);
if( cur.session && cur.session->puser )
{
cur.request->models.Add(L"user", *cur.session->puser);
}
if( cur.session && cur.session->puser )
{
cur.request->models.Add(L"user", *cur.session->puser);
}
if( cur.request->is_item )
{
cur.request->models.Add(L"item", cur.request->item);
}
if( cur.request->is_item )
{
cur.request->models.Add(L"item", cur.request->item);
}
}
}
@ -857,8 +854,7 @@ void App::Make()
return;
}
if( !cur.request->PrepareAnswerType() )
return;
cur.request->PrepareAnswerType();
if( cur.session->ip_ban && cur.session->ip_ban->IsIPBanned() )
{
@ -958,6 +954,7 @@ void App::ReadRequest()
cookie_parser.Parse(cur.request->env_http_cookie, cur.request->cookie_tab);
accept_encoding_parser.ParseAndLog(cur.request->env_http_accept_encoding);
accept_parser.Parse(cur.request->env_http_accept, cur.request->accept_mime_types);
if( config.log_env_variables )
LogEnvironmentVariables();
@ -987,7 +984,9 @@ void App::SetEnv(const char * name, std::wstring & env)
/*
* IMPROVE ME take it from cur.request.headers_in?
*/
void App::ReadEnvVariables()
{
SetEnv("REQUEST_METHOD", cur.request->env_request_method);
@ -1000,6 +999,7 @@ void App::ReadEnvVariables()
SetEnv("HTTP_USER_AGENT", cur.request->env_http_user_agent);
SetEnv("HTTP_COOKIE", cur.request->env_http_cookie);
SetEnv("HTTP_ACCEPT_ENCODING", cur.request->env_http_accept_encoding);
SetEnv("HTTP_ACCEPT", cur.request->env_http_accept);
}
@ -1116,7 +1116,6 @@ void App::ReadEnvRemoteIP()
void App::CheckRequestMethod()
{
cur.request->method = Request::unknown_method;
@ -1432,28 +1431,28 @@ void App::PrepareHeaderContentType()
{
if( !cur.request->out_headers.has_key(Winix::Header::content_type) )
{
if( cur.request->answer_source == Request::AnswerSource::answer_bin_stream )
if( cur.request->container_type == Request::ContainerType::container_json )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_octet_stream);
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_json_utf8);
}
else
if( cur.request->container_type == Request::ContainerType::container_xml )
{
if( cur.request->answer_container == Request::AnswerContainer::answer_json )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_json_utf8);
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_xml )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_xml_utf8);
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_csv )
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_xml_utf8);
}
else
if( cur.request->container_type == Request::ContainerType::container_csv )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::text_csv_utf8);
}
else
if( cur.request->container_type == Request::ContainerType::container_raw )
{
if( cur.request->send_bin_stream )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::text_csv_utf8);
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_octet_stream);
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_text )
{
switch( config.content_type_header )
{
@ -1828,77 +1827,197 @@ void App::SendAnswer()
// what about method HEAD?
if( !cur.request->redirect_to.empty() || !cur.request->x_sendfile.empty() )
{
Send8bitOutput(output_8bit);
Send8bitOutput(output_8bit); // send empty content
return;
}
if( cur.request->answer_source == Request::AnswerSource::answer_bin_stream )
plugin.Call(WINIX_CONTENT_MAKE);
if( cur.request->use_ezc_engine )
{
UseEzcGenerator();
}
if( cur.request->container_type == Request::ContainerType::container_raw )
{
PrepareRawAnswer();
}
else
if( cur.request->container_type == Request::ContainerType::container_json )
{
PrepareJsonAnswer();
}
else
if( cur.request->container_type == Request::ContainerType::container_xml )
{
PrepareXmlAnswer();
}
else
if( cur.request->container_type == Request::ContainerType::container_csv )
{
PrepareCsvAnswer();
}
Send8bitOutput(output_8bit);
}
void App::PrepareRawAnswer()
{
if( cur.request->send_bin_stream )
{
Send8bitOutput(cur.request->out_bin_stream);
}
else
if( cur.request->send_main_stream )
{
FilterHtmlIfNeeded(cur.request->out_main_stream.get_buffer(), output_8bit, false);
}
else
if( cur.request->send_all_frames )
{
// is this plugin call correct here?
plugin.Call(WINIX_CONTENT_MAKE);
SerializeAllFrames();
}
else
if( !cur.request->send_frames.empty() )
{
SerializeSpecificFrames();
}
}
if( cur.request->answer_source == Request::AnswerSource::answer_models && cur.request->use_ezc_engine )
{
MakeEzcGenerator(); // give me a better name
void App::PrepareJsonAnswer()
{
output_8bit << '{';
PrepareContenerizedAnswer();
output_8bit << '}';
}
if( !cur.request->frame.empty() || cur.request->send_all_frames )
{
cur.request->answer_source = Request::AnswerSource::answer_frame_streams;
}
else
{
cur.request->answer_source = Request::AnswerSource::answer_main_stream;
}
}
void App::PrepareXmlAnswer()
{
output_8bit << '<';
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
if( cur.request->answer_source == Request::AnswerSource::answer_main_stream )
{
const wchar_t * field_name = nullptr;
PrepareContenerizedAnswer();
output_8bit << "</";
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
}
void App::PrepareCsvAnswer()
{
PrepareContenerizedAnswer();
}
void App::PrepareContenerizedAnswer()
{
bool put_separator = false;
if( cur.request->serialize_models )
{
SerializeModels();
put_separator = true;
}
if( cur.request->answer_container == Request::AnswerContainer::answer_xml )
field_name = config.xml_root.c_str();
if( cur.request->send_bin_stream )
{
PutSeparatorIfNeeded(put_separator);
// IMPLEMENT ME serialize binary stream as base64 and put in 'bin_stream' field
pt::WTextStream str;
str << "NOT IMPLEMENTED YET";
SerializeStream(str, config.bin_stream_field.c_str());
put_separator = true;
}
if( cur.request->send_main_stream )
{
PutSeparatorIfNeeded(put_separator);
SerializeStream(cur.request->out_main_stream.get_buffer(), config.main_stream_field.c_str());
put_separator = true;
}
SerializeStream(cur.request->out_main_stream.get_buffer(), field_name);
if( cur.request->send_all_frames || !cur.request->send_frames.empty() )
{
PutSeparatorIfNeeded(put_separator);
SerializeFieldJson(config.ezc_frames_field.c_str());
output_8bit << "{";
if( cur.request->send_all_frames )
{
SerializeAllFrames();
}
else
if( cur.request->answer_source == Request::AnswerSource::answer_frame_streams )
if( !cur.request->send_frames.empty() )
{
SerializeFrames();
SerializeSpecificFrames();
}
else
if( cur.request->answer_source == Request::AnswerSource::answer_models )
output_8bit << "}";
put_separator = true;
}
}
void App::PutSeparatorIfNeeded(bool put_separator)
{
if( put_separator )
{
switch( cur.request->container_type )
{
SerializeModels();
case Request::ContainerType::container_json:
output_8bit << ",";
break;
case Request::ContainerType::container_xml:
break;
case Request::ContainerType::container_csv:
output_8bit << ";";
break;
case Request::ContainerType::container_raw:
default:
break;
}
}
}
Send8bitOutput(output_8bit);
void App::SerializeFieldJson(const wchar_t * field_name)
{
if( field_name )
{
output_8bit << '"';
pt::esc_to_json(field_name, output_8bit);
output_8bit << "\":";
}
}
void App::SerializeStream(const pt::WTextStream & input_stream, const wchar_t * field_name)
{
switch( cur.request->answer_container )
switch( cur.request->container_type )
{
case Request::AnswerContainer::answer_json:
case Request::ContainerType::container_json:
SerializeStreamJson(input_stream, field_name);
break;
case Request::AnswerContainer::answer_xml:
case Request::ContainerType::container_xml:
SerializeStreamXml(input_stream, field_name);
break;
case Request::AnswerContainer::answer_csv:
case Request::ContainerType::container_csv:
SerializeStreamCsv(input_stream, field_name);
break;
case Request::AnswerContainer::answer_text:
case Request::ContainerType::container_raw:
default:
FilterHtmlIfNeeded(input_stream, output_8bit);
FilterHtmlIfNeeded(input_stream, output_8bit, false);
break;
}
}
@ -1906,13 +2025,7 @@ void App::SerializeStream(const pt::WTextStream & input_stream, const wchar_t *
void App::SerializeStreamJson(const pt::WTextStream & input_stream, const wchar_t * field_name)
{
if( field_name )
{
output_8bit << '"';
pt::esc_to_json(field_name, output_8bit);
output_8bit << "\":";
}
SerializeFieldJson(field_name);
output_8bit << '"';
if( config.html_filter && cur.request->use_html_filter )
@ -1982,39 +2095,6 @@ void App::SerializeStreamCsv(const pt::WTextStream & input_stream, const wchar_t
}
void App::SerializeFrames()
{
if( cur.request->answer_container == Request::AnswerContainer::answer_json )
{
output_8bit << '{';
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_xml )
{
output_8bit << '<';
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
}
if( cur.request->frame.empty() || cur.request->send_all_frames )
SerializeAllFrames();
else
SerializeOneFrame();
if( cur.request->answer_container == Request::AnswerContainer::answer_json )
{
output_8bit << '}';
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_xml )
{
output_8bit << "</";
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
}
}
void App::SerializeAllFrames()
{
auto i = cur.request->out_streams.streams_map.begin();
@ -2022,12 +2102,12 @@ void App::SerializeAllFrames()
for( ; i != cur.request->out_streams.streams_map.end() ; ++i)
{
if( cur.request->answer_container == Request::AnswerContainer::answer_json && !is_first )
if( cur.request->container_type == Request::ContainerType::container_json && !is_first )
{
output_8bit << ',';
}
if( cur.request->answer_container == Request::AnswerContainer::answer_xml && i->first.empty() )
if( cur.request->container_type == Request::ContainerType::container_xml && i->first.empty() )
{
log << log2 << "App: I cannot serialize a frame with an empty name to xml (frame skipped)" << logend;
}
@ -2041,51 +2121,46 @@ void App::SerializeAllFrames()
}
void App::SerializeOneFrame()
void App::SerializeSpecificFrames()
{
auto i = cur.request->out_streams.streams_map.find(cur.request->frame);
bool is_first = true;
if( i != cur.request->out_streams.streams_map.end() )
for(std::wstring & frame: cur.request->send_frames)
{
SerializeStream(i->second->get_buffer(), cur.request->frame.c_str());
}
else
{
log << log2 << "App: there is no such a frame: " << cur.request->frame << logend;
// return 404 in such a case?
auto i = cur.request->out_streams.streams_map.find(frame);
if( i != cur.request->out_streams.streams_map.end() )
{
if( cur.request->container_type == Request::ContainerType::container_json && !is_first )
{
output_8bit << ',';
}
SerializeStream(i->second->get_buffer(), frame.c_str());
is_first = false;
}
else
{
log << log2 << "App: there is no such a frame: " << frame << logend;
}
}
}
void App::SerializeModels()
{
Ezc::Models::ModelsMap models_map = cur.request->models.GetMap();
auto i = models_map.begin();
if( cur.request->answer_container == Request::AnswerContainer::answer_json )
{
output_8bit << '{';
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_xml )
{
output_8bit << '<';
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
log << log2 << "App: serializing models to xml not implemented yet" << logend;
}
bool is_first = true;
for( ; i != models_map.end() ; ++i)
{
if( cur.request->answer_container == Request::AnswerContainer::answer_json && !is_first )
if( cur.request->container_type == Request::ContainerType::container_json && !is_first )
{
output_8bit << ',';
}
if( cur.request->answer_container == Request::AnswerContainer::answer_xml && i->first.empty() )
if( cur.request->container_type == Request::ContainerType::container_xml && i->first.empty() )
{
log << log2 << "App: I cannot serialize a model with an empty name to xml (model skipped)" << logend;
}
@ -2096,40 +2171,27 @@ void App::SerializeModels()
is_first = false;
}
if( cur.request->answer_container == Request::AnswerContainer::answer_json )
{
output_8bit << '}';
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_xml )
{
output_8bit << "</";
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
}
}
void App::SerializeModel(morm::Wrapper & wrapper, const wchar_t * field_name)
{
switch( cur.request->answer_container )
switch( cur.request->container_type )
{
case Request::AnswerContainer::answer_json:
case Request::ContainerType::container_json:
SerializeModelJson(wrapper, field_name);
break;
case Request::AnswerContainer::answer_xml:
case Request::ContainerType::container_xml:
SerializeModelXml(wrapper, field_name);
break;
case Request::AnswerContainer::answer_csv:
case Request::ContainerType::container_csv:
SerializeModelCsv(wrapper, field_name);
break;
case Request::AnswerContainer::answer_text:
case Request::ContainerType::container_raw:
default:
SerializeModelCsv(wrapper, field_name);
break;
}
}
@ -2137,12 +2199,7 @@ void App::SerializeModel(morm::Wrapper & wrapper, const wchar_t * field_name)
void App::SerializeModelJson(morm::Wrapper & wrapper, const wchar_t * field_name)
{
if( field_name )
{
output_8bit << '"';
pt::esc_to_json(field_name, output_8bit);
output_8bit << "\":";
}
SerializeFieldJson(field_name);
if( wrapper.model )
{

18
winixd/core/app.h

@ -54,6 +54,7 @@
#include "cookieparser.h"
#include "postmultiparser.h"
#include "acceptencodingparser.h"
#include "acceptparser.h"
#include "winixrequest.h"
#include "log/log.h"
#include "filelog.h"
@ -140,6 +141,7 @@ private:
CookieParser cookie_parser;
AcceptEncodingParser accept_encoding_parser;
AcceptParser accept_parser;
Compress compress;
FCGX_Request fcgi_request;
int fcgi_socket;
@ -199,7 +201,7 @@ private:
void CheckIfNeedSSLredirect();
void SetLocale();
void CheckPostRedirect();
void MakeEzcGenerator();
void UseEzcGenerator();
void AddDefaultModels();
void Make();
void SaveSessionsIfNeeded(); // !! IMPROVE ME wywalic do menagera sesji??
@ -207,14 +209,24 @@ private:
void SendData(const BinaryPage & page, FCGX_Stream * out);
void ReadRequest();
void SendAnswer();
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 SerializeFrames();
void SerializeAllFrames();
void SerializeOneFrame();
void SerializeSpecificFrames();
void SerializeModels();
void Send8bitOutput(BinaryPage & output);

11
winixd/core/config.cpp

@ -284,10 +284,17 @@ void Config::AssignValues(bool stdout_is_closed)
ezc_max_elements = Size(L"ezc_max_elements", 50000);
ezc_max_loop_elements = Size(L"ezc_max_loop_elements", 5000);
ezc_out_streams_size = Size(L"ezc_out_streams_size", 128);
request_frame_parameter = Text(L"request_frame_parameter", L"frame");
request_all_frames_parameter = Text(L"request_all_frames_parameter", L"allframes");
request_frame_parameter = Text(L"request_frame_parameter", L"frame");
request_all_frames_parameter = Text(L"request_all_frames_parameter", L"all_frames");
request_main_stream_parameter = Text(L"request_main_stream_parameter", L"main_stream");
request_frame_parameter_max_length = Size(L"request_frame_parameter_max_length", 128);
request_frame_parameter_max_frames = Size(L"request_frame_parameter_max_frames", 16);
xml_root = Text(L"xml_root", L"winix");
bin_stream_field = Text(L"bin_stream_field", L"bin_stream");
main_stream_field = Text(L"main_stream_field", L"main_stream");
ezc_frames_field = Text(L"ezc_frames_field", L"ezc_frames");
account_need_email_verification = Bool(L"account_need_email_verification", true);
reset_password_code_expiration_time = Long(L"reset_password_code_expiration_time", 86400);

31
winixd/core/config.h

@ -676,15 +676,40 @@ public:
// default: frame
std::wstring request_frame_parameter;
// the name of the url parameter for returning all frames, e.g. https://domain.tld/mydir/myfunction/allframes
// default: allframes
// the name of the url parameter for returning all frames, e.g. https://domain.tld/mydir/myfunction/all_frames
// default: all_frames
std::wstring request_all_frames_parameter;
// the name of the url parameter for returning the main ezc stream, e.g. https://domain.tld/mydir/myfunction/main_stream
// default: main_stream
std::wstring request_main_stream_parameter;
// max lenght of the url frame parameter
// default: 128
size_t request_frame_parameter_max_length;
// max number of frames in the frame url parameter (they are separated by a comma)
// default: 16
// if you need more frames you can use all_frames url parameter to return all frames
size_t request_frame_parameter_max_frames;
// the name of the root element when serializing request answer to xml
// default: winix
std::wstring xml_root;
// when true then when a user want to create a new account
// the name of the field of the binary stream when serializing a request
// default: bin_stream
std::wstring bin_stream_field;
// the name of the field of the main ezc stream when serializing a request
// default: main_stream
std::wstring main_stream_field;
// the name of the field (object) of the ezc frames when serializing a request
// default: ezc_frames
std::wstring ezc_frames_field;
// when true then when a user want to create a new account
// he has to provide his email and a message will be sent back to him
// with a link to activate the account
// default: true

49
winixd/core/header.h

@ -5,7 +5,7 @@
*/
/*
* Copyright (c) 2021, Tomasz Sowa
* Copyright (c) 2021-2022, Tomasz Sowa
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -35,6 +35,8 @@
#ifndef headerfile_winix_core_header
#define headerfile_winix_core_header
#include "log.h"
namespace Winix
{
@ -47,7 +49,13 @@ public:
* headers' names
*/
static constexpr const wchar_t * content_type = L"Content-Type";
static constexpr const wchar_t * accept = L"Accept";
/*
* headers' names lower case
*/
static constexpr const wchar_t * content_type_lc = L"content-type";
static constexpr const wchar_t * accept_lc = L"accept";
/*
* headers' values
@ -61,6 +69,10 @@ public:
static constexpr const wchar_t * text_csv = L"text/csv";
static constexpr const wchar_t * text_javascript = L"text/javascript";
static constexpr const wchar_t * all_all = L"*/*";
static constexpr const wchar_t * text_all = L"text/*";
static constexpr const wchar_t * application_all = L"application/*";
static constexpr const wchar_t * text_html_utf8 = L"text/html; charset=UTF-8";
static constexpr const wchar_t * application_json_utf8 = L"application/json; charset=UTF-8";
static constexpr const wchar_t * application_xml_utf8 = L"application/xml; charset=UTF-8";
@ -68,6 +80,41 @@ public:
static constexpr const wchar_t * text_csv_utf8 = L"text/csv; charset=UTF-8";
static constexpr const wchar_t * text_javascript_utf8 = L"text/javascript; charset=UTF-8";
};
class HeaderValue
{
public:
std::wstring value;
double weight; // q parameter in some headers
HeaderValue()
{
weight = 0.0;
}
static void log_values(const std::vector<HeaderValue> & header_values, Log & log)
{
char buf[64];
bool is_first = true;
for(const HeaderValue & h: header_values)
{
snprintf(buf, sizeof(buf)/sizeof(char), "%.3f", h.weight);
if( !is_first )
log << ", ";
log << h.value << ";q=" << buf;
is_first = false;
}
}
};
}

31
winixd/core/misc.cpp

@ -1601,5 +1601,36 @@ void timespec_to_stream_with_unit(timespec & val, pt::Stream & stream)
}
void slice_by(const std::wstring & str, wchar_t c, std::vector<std::wstring> & out)
{
std::wstring tmp;
for(size_t i = 0 ; i < str.size() ; ++i)
{
if( str[i] == c )
{
if( !tmp.empty() )
{
out.push_back(tmp);
tmp.clear();
}
}
else
{
tmp += str[i];
}
}
if( !tmp.empty() )
{
out.push_back(tmp);
}
}
} // namespace Winix

2
winixd/core/misc.h

@ -1029,6 +1029,8 @@ void timespec_to_stream(timespec & val, pt::Stream & stream);
void timespec_to_stream_with_unit(timespec & val, pt::Stream & stream);
void slice_by(const std::wstring & str, wchar_t c, std::vector<std::wstring> & out);
} // namespace Winix

173
winixd/core/request.cpp

@ -120,6 +120,7 @@ void Request::Clear()
env_http_host.clear();
env_http_user_agent.clear();
env_http_accept_encoding.clear();
env_http_accept.clear();
env_fcgi_role.clear();
env_content_type.clear();
env_https.clear();
@ -134,11 +135,16 @@ void Request::Clear()
param_tab.clear();
anchor.clear();
answer_source = AnswerSource::answer_models;
answer_container = AnswerContainer::answer_text;
use_ezc_engine = true;
frame.clear();
send_bin_stream = false;
send_main_stream = false;
send_all_frames = false;
send_frames.clear();
use_ezc_engine = false;
serialize_models = false;
accept_mime_types.clear();
container_type = ContainerType::container_raw;
status = WINIX_ERR_OK;
@ -206,30 +212,81 @@ void Request::RequestEnds()
bool Request::PrepareAnswerType()
void Request::PrepareAnswerType()
{
answer_source = AnswerSource::answer_models;
answer_container = AnswerContainer::answer_text;
use_ezc_engine = true;
CheckAcceptHeader();
CheckContainerParameter();
frame = ParamValue(config->request_frame_parameter);
serialize_models = (container_type != ContainerType::container_raw);
send_all_frames = (ParamValuep(config->request_all_frames_parameter) != nullptr);
send_main_stream = (ParamValuep(config->request_main_stream_parameter) != nullptr);
use_html_filter = config->html_filter;
PrepareFrameNames();
if( container_type == ContainerType::container_raw && !send_all_frames && send_frames.empty() )
{
send_main_stream = true;
}
use_ezc_engine = send_main_stream || send_all_frames || !send_frames.empty();
}
bool ok = true;
// IMPLEMENT ME add checking for Accept header;
void Request::CheckAcceptHeader()
{
if( !accept_mime_types.empty() )
{
bool found = false;
ok = ok && CheckContainerParameter();
ok = ok && CheckAnswerParameter();
for(HeaderValue & h: accept_mime_types)
{
if( h.value == Header::text_html || h.value == Header::application_xhtml_xml ||
h.value == Header::text_all || h.value == Header::all_all)
{
container_type = ContainerType::container_raw;
found = true;
break;
}
else
if( h.value == Header::application_json || h.value == Header::application_all )
{
container_type = ContainerType::container_json;
found = true;
break;
}
else
if( h.value == Header::application_xml )
{
container_type = ContainerType::container_xml;
found = true;
break;
}
else
if( h.value == Header::text_csv )
{
container_type = ContainerType::container_csv;
found = true;
break;
}
}
if( !found )
{
Log * log = get_logger();
return ok;
if( log )
{
(*log) << log2 << "App: an unknown " << Header::accept << " headers: ";
HeaderValue::log_values(accept_mime_types, *log);
(*log) << " (skipping)" << logend;
}
}
}
}
// IMPROVE ME give me a better name
bool Request::CheckContainerParameter()
void Request::CheckContainerParameter()
{
std::wstring * container = ParamValuep(L"container");
@ -238,24 +295,24 @@ bool Request::CheckContainerParameter()
// IMPROVEME do a plugin call here
// if a plugin can consume this then don't check text/json/xml/csv and just return true
if( *container == L"text" )
if( *container == L"raw" )
{
answer_container = Request::AnswerContainer::answer_text;
container_type = ContainerType::container_raw;
}
else
if( *container == L"json" )
{
answer_container = Request::AnswerContainer::answer_json;
container_type = ContainerType::container_json;
}
else
if( *container == L"xml" )
{
answer_container = Request::AnswerContainer::answer_xml;
container_type = ContainerType::container_xml;
}
else
if( *container == L"csv" )
{
answer_container = Request::AnswerContainer::answer_csv;
container_type = ContainerType::container_csv;
}
else
{
@ -263,50 +320,43 @@ bool Request::CheckContainerParameter()
if( log )
{
(*log) << log2 << "App: an unknown 'container' parameter: " << *container << logend;
// IMPROVE ME set status to 404
return false;
(*log) << log2 << "App: an unknown container url parameter: " << *container << " (skipping)" << logend;
}
}
}
return true;
}
// IMPROVE ME give me a better name
bool Request::CheckAnswerParameter()
void Request::PrepareFrameNames()
{
std::wstring * answer = ParamValuep(L"answer");
Config * config = get_config();
Log * log = get_logger();
if( answer )
if( config && log )
{
// IMPROVEME do a plugin call here
// if a plugin can consume this then don't check html/data and just return true
const std::wstring & frame = ParamValue(config->request_frame_parameter);
if( *answer == L"html" )
{
use_ezc_engine = true;
}
else
if( *answer == L"data" )
{
use_ezc_engine = false;
}
else
if( frame.size() <= config->request_frame_parameter_max_length )
{
Log * log = get_logger();
send_frames.clear();
slice_by(frame, ',', send_frames);
std::sort(send_frames.begin(), send_frames.end());
auto frames_end = std::unique(send_frames.begin(), send_frames.end());
send_frames.erase(frames_end, send_frames.end());
if( log )
if( send_frames.size() > config->request_frame_parameter_max_frames )
{
(*log) << log2 << "App: an unknown 'answer' parameter: " << *answer << logend;
// IMPROVE ME set status to 404
return false;
send_frames.clear();
(*log) << log2 << "Request: the number of frames exceeds " << config->request_frame_parameter_max_frames
<< " (skipping frames)" << logend;
}
}
else
{
(*log) << log2 << "Request: the length of the frame url parameter exceeds " << config->request_frame_parameter_max_length
<< " characters (skiping frames)" << logend;
}
}
return true;
}
@ -504,6 +554,31 @@ void Request::last_item_wrapper(morm::Wrapper & wrapper)
}
bool Request::has_frame(const wchar_t * frame)
{
for(std::wstring & f: send_frames)
{
if( f == frame )
return true;
}
return false;
}
bool Request::has_frame(const std::wstring & frame)
{
for(std::wstring & f: send_frames)
{
if( f == frame )
return true;
}
return false;
}
} // namespace Winix

216
winixd/core/request.h

@ -190,6 +190,7 @@ public:
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;
@ -249,180 +250,87 @@ public:
*
*/
// the algorithm how a request's answer is created
// ------------------------------------------------------------------------------------------
//
// at the beginning of a request winix sets
// answer_source to models
// answer_container to text
// use_ezc_engine to true
// the algorithm how a request's container is selected is shown below:
// (the whole answer's algorightm is implemented in PrepareAnswerType() method)
// ------------------------------------------------------------------------------------------
//
// next answer_container and use_ezc_engine can be changed in the following way:
// 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:
//
// 1. winix will look for 'Accept' http header and depending on the header winix will set:
// Accept | container_type
// -----------------------------------------------
// text/html | container_raw
// application/xhtml+xml | container_raw
// application/json | container_json
// application/xml | container_xml
// text/csv | container_csv
//
// Accept | answer_container | use_ezc_engine
// ------------------------------------|-----------------
// application/json | json | false
// application/xml | xml | false
// text/csv | csv | false
// 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
//
//
// 2. next answer_container is set depending on 'container' url parameter
// container | answer_container
// ---------------------------------------------------------
// not present | don't change the value
// text | text
// json | json
// xml | xml
// csv | csv
// Samples:
//
// use_ezc_engine is set depending on 'answer' url parameter:
// answer | use_ezc_engine
// ---------------------------------
// not present | don't change the value
// html | true
// data | false
// http://domain.tld/dir/controller
// returns html answer from the main ezc stream
//
// if 'answer' is html then we take into account two more parameters:
// frame: frame_name (empty default) - if set then winix returns this specific frame
// allframes: (if present then winix returns all frames)
// 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