aboutsummaryrefslogtreecommitdiff
path: root/mod
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2018-07-07 19:09:53 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2018-07-10 22:03:18 +0300
commit21033565488f6c63b4c40962cccfdc8b6ca32b2a (patch)
tree44732ab7e1c7a7b25e64b82bf61d293f6cff2f86 /mod
parent026377d0c145277b24b3af5fdcf707222e854bd3 (diff)
Add support for package submission
Diffstat (limited to 'mod')
-rw-r--r--mod/database-module.cxx6
-rw-r--r--mod/database-module.hxx8
-rw-r--r--mod/mod-build-force.cxx6
-rw-r--r--mod/mod-build-log.cxx6
-rw-r--r--mod/mod-build-result.cxx13
-rw-r--r--mod/mod-build-task.cxx13
-rw-r--r--mod/mod-builds.cxx6
-rw-r--r--mod/mod-package-details.cxx6
-rw-r--r--mod/mod-package-search.cxx11
-rw-r--r--mod/mod-package-version-details.cxx6
-rw-r--r--mod/mod-repository-details.cxx6
-rw-r--r--mod/mod-repository-root.cxx123
-rw-r--r--mod/mod-repository-root.hxx8
-rw-r--r--mod/mod-submit.cxx715
-rw-r--r--mod/mod-submit.hxx45
-rw-r--r--mod/module.cxx68
-rw-r--r--mod/module.hxx32
-rw-r--r--mod/options.cli34
-rw-r--r--mod/page.cxx9
19 files changed, 976 insertions, 145 deletions
diff --git a/mod/database-module.cxx b/mod/database-module.cxx
index 22a4d1e..137d7ef 100644
--- a/mod/database-module.cxx
+++ b/mod/database-module.cxx
@@ -28,7 +28,7 @@ namespace brep
//
database_module::
database_module (const database_module& r)
- : module (r),
+ : handler (r),
retry_ (r.retry_),
package_db_ (r.initialized_ ? r.package_db_ : nullptr),
build_db_ (r.initialized_ ? r.build_db_ : nullptr),
@@ -104,13 +104,13 @@ namespace brep
handle (request& rq, response& rs, log& l)
try
{
- return module::handle (rq, rs, l);
+ return handler::handle (rq, rs, l);
}
catch (const odb::recoverable& e)
{
if (retry_-- > 0)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
l1 ([&]{trace << e << "; " << retry_ + 1 << " retries left";});
throw retry ();
}
diff --git a/mod/database-module.hxx b/mod/database-module.hxx
index 9d1ef4c..70ae004 100644
--- a/mod/database-module.hxx
+++ b/mod/database-module.hxx
@@ -22,11 +22,11 @@
namespace brep
{
- // A module that utilises the database. Specifically, it will retry the
+ // A handler that utilises the database. Specifically, it will retry the
// request in the face of recoverable database failures (deadlock, loss of
// connection, etc) up to a certain number of times.
//
- class database_module: public module
+ class database_module: public handler
{
protected:
database_module () = default;
@@ -38,10 +38,10 @@ namespace brep
database_module (const database_module&);
// Required to avoid getting warning from clang that
- // database_module::init() hides module::init() virtual functions. This
+ // database_module::init() hides handler::init() virtual functions. This
// way all functions get to the same scope and become overloaded set.
//
- using module::init;
+ using handler::init;
// Initialize the package database instance. Throw odb::exception on
// failure.
diff --git a/mod/mod-build-force.cxx b/mod/mod-build-force.cxx
index af47b4c..b6514ce 100644
--- a/mod/mod-build-force.cxx
+++ b/mod/mod-build-force.cxx
@@ -35,7 +35,7 @@ build_force (const build_force& r)
void brep::build_force::
init (scanner& s)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
options_ = make_shared<options::build_force> (
s, unknown_mode::fail, unknown_mode::fail);
@@ -51,7 +51,7 @@ handle (request& rq, response& rs)
{
using brep::version; // Not to confuse with module::version.
- MODULE_DIAG;
+ HANDLER_DIAG;
if (build_db_ == nullptr)
throw invalid_request (501, "not implemented");
@@ -60,7 +60,7 @@ handle (request& rq, response& rs)
try
{
- name_value_scanner s (rq.parameters ());
+ name_value_scanner s (rq.parameters (8 * 1024));
params = params::build_force (s, unknown_mode::fail, unknown_mode::fail);
}
catch (const cli::exception& e)
diff --git a/mod/mod-build-log.cxx b/mod/mod-build-log.cxx
index c1eec4c..ab9ab12 100644
--- a/mod/mod-build-log.cxx
+++ b/mod/mod-build-log.cxx
@@ -37,7 +37,7 @@ build_log (const build_log& r)
void brep::build_log::
init (scanner& s)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
options_ = make_shared<options::build_log> (
s, unknown_mode::fail, unknown_mode::fail);
@@ -56,7 +56,7 @@ handle (request& rq, response& rs)
{
using brep::version; // Not to confuse with module::version.
- MODULE_DIAG;
+ HANDLER_DIAG;
if (build_db_ == nullptr)
throw invalid_request (501, "not implemented");
@@ -146,7 +146,7 @@ handle (request& rq, response& rs)
//
try
{
- name_value_scanner s (rq.parameters ());
+ name_value_scanner s (rq.parameters (1024));
params::build_log (s, unknown_mode::fail, unknown_mode::fail);
}
catch (const cli::exception& e)
diff --git a/mod/mod-build-result.cxx b/mod/mod-build-result.cxx
index 7891fe1..65e8425 100644
--- a/mod/mod-build-result.cxx
+++ b/mod/mod-build-result.cxx
@@ -46,7 +46,7 @@ build_result (const build_result& r)
void brep::build_result::
init (scanner& s)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
options_ = make_shared<options::build_result> (
s, unknown_mode::fail, unknown_mode::fail);
@@ -68,7 +68,7 @@ handle (request& rq, response&)
{
using brep::version; // Not to confuse with module::version.
- MODULE_DIAG;
+ HANDLER_DIAG;
if (build_db_ == nullptr)
throw invalid_request (501, "not implemented");
@@ -77,7 +77,10 @@ handle (request& rq, response&)
//
try
{
- name_value_scanner s (rq.parameters ());
+ // Note that we expect the result request manifest to be posted and so
+ // consider parameters from the URL only.
+ //
+ name_value_scanner s (rq.parameters (0 /* limit */, true /* url_only */));
params::build_result (s, unknown_mode::fail, unknown_mode::fail);
}
catch (const cli::exception& e)
@@ -89,6 +92,10 @@ handle (request& rq, response&)
try
{
+ // We fully cache the request content to be able to retry the request
+ // handling if odb::recoverable is thrown (see database-module.cxx for
+ // details).
+ //
size_t limit (options_->build_result_request_max_size ());
manifest_parser p (rq.content (limit, limit), "result_request_manifest");
rqm = result_request_manifest (p);
diff --git a/mod/mod-build-task.cxx b/mod/mod-build-task.cxx
index f1e4cdb..4e56d02 100644
--- a/mod/mod-build-task.cxx
+++ b/mod/mod-build-task.cxx
@@ -52,7 +52,7 @@ build_task (const build_task& r)
void brep::build_task::
init (scanner& s)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
options_ = make_shared<options::build_task> (
s, unknown_mode::fail, unknown_mode::fail);
@@ -81,7 +81,7 @@ init (scanner& s)
bool brep::build_task::
handle (request& rq, response& rs)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
if (build_db_ == nullptr)
throw invalid_request (501, "not implemented");
@@ -90,7 +90,10 @@ handle (request& rq, response& rs)
try
{
- name_value_scanner s (rq.parameters ());
+ // Note that we expect the task request manifest to be posted and so
+ // consider parameters from the URL only.
+ //
+ name_value_scanner s (rq.parameters (0 /* limit */, true /* url_only */));
params = params::build_task (s, unknown_mode::fail, unknown_mode::fail);
}
catch (const cli::exception& e)
@@ -102,6 +105,10 @@ handle (request& rq, response& rs)
try
{
+ // We fully cache the request content to be able to retry the request
+ // handling if odb::recoverable is thrown (see database-module.cxx for
+ // details).
+ //
size_t limit (options_->build_task_request_max_size ());
manifest_parser p (rq.content (limit, limit), "task_request_manifest");
tqm = task_request_manifest (p);
diff --git a/mod/mod-builds.cxx b/mod/mod-builds.cxx
index e43739f..f255b25 100644
--- a/mod/mod-builds.cxx
+++ b/mod/mod-builds.cxx
@@ -50,7 +50,7 @@ builds (const builds& r)
void brep::builds::
init (scanner& s)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
options_ = make_shared<options::builds> (
s, unknown_mode::fail, unknown_mode::fail);
@@ -264,7 +264,7 @@ handle (request& rq, response& rs)
using brep::version;
using namespace web::xhtml;
- MODULE_DIAG;
+ HANDLER_DIAG;
if (build_db_ == nullptr)
throw invalid_request (501, "not implemented");
@@ -277,7 +277,7 @@ handle (request& rq, response& rs)
try
{
- name_value_scanner s (rq.parameters ());
+ name_value_scanner s (rq.parameters (8 * 1024));
params = params::builds (s, unknown_mode::fail, unknown_mode::fail);
}
catch (const cli::exception& e)
diff --git a/mod/mod-package-details.cxx b/mod/mod-package-details.cxx
index a348d95..ffd0ae7 100644
--- a/mod/mod-package-details.cxx
+++ b/mod/mod-package-details.cxx
@@ -37,7 +37,7 @@ package_details (const package_details& r)
void brep::package_details::
init (scanner& s)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
options_ = make_shared<options::package_details> (
s, unknown_mode::fail, unknown_mode::fail);
@@ -69,7 +69,7 @@ handle (request& rq, response& rs)
using namespace web;
using namespace web::xhtml;
- MODULE_DIAG;
+ HANDLER_DIAG;
const size_t res_page (options_->search_results ());
const dir_path& root (options_->root ());
@@ -79,7 +79,7 @@ handle (request& rq, response& rs)
try
{
- name_value_scanner s (rq.parameters ());
+ name_value_scanner s (rq.parameters (8 * 1024));
params = params::package_details (
s, unknown_mode::fail, unknown_mode::fail);
diff --git a/mod/mod-package-search.cxx b/mod/mod-package-search.cxx
index d7a2b98..d53397e 100644
--- a/mod/mod-package-search.cxx
+++ b/mod/mod-package-search.cxx
@@ -38,7 +38,7 @@ package_search (const package_search& r)
void brep::package_search::
init (scanner& s)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
options_ = make_shared<options::package_search> (
s, unknown_mode::fail, unknown_mode::fail);
@@ -82,7 +82,7 @@ handle (request& rq, response& rs)
{
using namespace web::xhtml;
- MODULE_DIAG;
+ HANDLER_DIAG;
const size_t res_page (options_->search_results ());
const dir_path& root (options_->root ());
@@ -92,7 +92,7 @@ handle (request& rq, response& rs)
try
{
- name_value_scanner s (rq.parameters ());
+ name_value_scanner s (rq.parameters (8 * 1024));
params = params::package_search (
s, unknown_mode::fail, unknown_mode::fail);
}
@@ -125,7 +125,10 @@ handle (request& rq, response& rs)
// element of the search form. The problem appears in Firefox and has a
// (4-year old, at the time of this writing) bug report:
//
- // https://bugzilla.mozilla.org/show_bug.cgi?id=712130.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=712130
+ //
+ // @@ An update: claimed to be fixed in Firefox 60 that is released in
+ // May 2018. Is it time to cleanup? Remember to cleanup in all places.
//
<< SCRIPT << " " << ~SCRIPT
<< ~HEAD
diff --git a/mod/mod-package-version-details.cxx b/mod/mod-package-version-details.cxx
index cef9357..833b802 100644
--- a/mod/mod-package-version-details.cxx
+++ b/mod/mod-package-version-details.cxx
@@ -43,7 +43,7 @@ package_version_details (const package_version_details& r)
void brep::package_version_details::
init (scanner& s)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
options_ = make_shared<options::package_version_details> (
s, unknown_mode::fail, unknown_mode::fail);
@@ -66,7 +66,7 @@ handle (request& rq, response& rs)
using namespace web::xhtml;
using brep::version; // Not to confuse with module::version.
- MODULE_DIAG;
+ HANDLER_DIAG;
const string& host (options_->host ());
const dir_path& root (options_->root ());
@@ -101,7 +101,7 @@ handle (request& rq, response& rs)
try
{
- name_value_scanner s (rq.parameters ());
+ name_value_scanner s (rq.parameters (1024));
params = params::package_version_details (
s, unknown_mode::fail, unknown_mode::fail);
diff --git a/mod/mod-repository-details.cxx b/mod/mod-repository-details.cxx
index 6043328..36d5508 100644
--- a/mod/mod-repository-details.cxx
+++ b/mod/mod-repository-details.cxx
@@ -41,7 +41,7 @@ repository_details (const repository_details& r)
void brep::repository_details::
init (scanner& s)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
options_ = make_shared<options::repository_details> (
s, unknown_mode::fail, unknown_mode::fail);
@@ -57,7 +57,7 @@ handle (request& rq, response& rs)
{
using namespace web::xhtml;
- MODULE_DIAG;
+ HANDLER_DIAG;
const dir_path& root (options_->root ());
@@ -65,7 +65,7 @@ handle (request& rq, response& rs)
//
try
{
- name_value_scanner s (rq.parameters ());
+ name_value_scanner s (rq.parameters (1024));
params::repository_details (s, unknown_mode::fail, unknown_mode::fail);
}
catch (const cli::exception& e)
diff --git a/mod/mod-repository-root.cxx b/mod/mod-repository-root.cxx
index aaf6988..367b137 100644
--- a/mod/mod-repository-root.cxx
+++ b/mod/mod-repository-root.cxx
@@ -12,6 +12,7 @@
#include <mod/module.hxx>
#include <mod/options.hxx>
+#include <mod/mod-submit.hxx>
#include <mod/mod-builds.hxx>
#include <mod/mod-build-log.hxx>
#include <mod/mod-build-task.hxx>
@@ -27,30 +28,66 @@ using namespace brep::cli;
namespace brep
{
- // request_proxy
+ // Request proxy. Removes the first parameter that is assumed to be a
+ // function name.
//
class request_proxy: public request
{
public:
- request_proxy (request& r, const name_values& p)
- : request_ (r), parameters_ (p) {}
+ request_proxy (request& r): request_ (r) {}
virtual const path_type&
path () {return request_.path ();}
virtual const name_values&
- parameters () {return parameters_;}
+ parameters (size_t limit, bool url_only)
+ {
+ if (!parameters_ || url_only < url_only_parameters_)
+ {
+ parameters_ = request_.parameters (limit, url_only);
+
+ assert (!parameters_->empty ()); // Always starts with a function name.
+ parameters_->erase (parameters_->begin ());
+
+ url_only_parameters_ = url_only;
+ }
+
+ return *parameters_;
+ }
+
+ istream&
+ open_upload (size_t index)
+ {
+ // The original request object still contains the function name entry,
+ // so we shift the index.
+ //
+ return request_.open_upload (index + 1);
+ }
+
+ istream&
+ open_upload (const string& name)
+ {
+ // We don't expect the function name here as a parameter name.
+ //
+ return request_.open_upload (name);
+ }
+
+ virtual const name_values&
+ headers () {return request_.headers ();}
virtual const name_values&
cookies () {return request_.cookies ();}
virtual istream&
- content (size_t limit, size_t buffer) {
- return request_.content (limit, buffer);}
+ content (size_t limit, size_t buffer)
+ {
+ return request_.content (limit, buffer);
+ }
private:
request& request_;
- const name_values& parameters_;
+ optional<name_values> parameters_;
+ bool url_only_parameters_; // Meaningless if parameters_ is not present.
};
// repository_root
@@ -65,15 +102,16 @@ namespace brep
build_result_ (make_shared<build_result> ()),
build_force_ (make_shared<build_force> ()),
build_log_ (make_shared<build_log> ()),
- builds_ (make_shared<builds> ())
+ builds_ (make_shared<builds> ()),
+ submit_ (make_shared<submit> ())
{
}
repository_root::
repository_root (const repository_root& r)
- : module (r),
+ : handler (r),
//
- // Deep/shallow-copy sub-modules depending on whether this is an
+ // Deep/shallow-copy sub-handlers depending on whether this is an
// exemplar/handler.
//
package_search_ (
@@ -113,6 +151,10 @@ namespace brep
r.initialized_
? r.builds_
: make_shared<builds> (*r.builds_)),
+ submit_ (
+ r.initialized_
+ ? r.submit_
+ : make_shared<submit> (*r.submit_)),
options_ (
r.initialized_
? r.options_
@@ -120,13 +162,13 @@ namespace brep
{
}
- // Return amalgamation of repository_root and all its sub-modules option
+ // Return amalgamation of repository_root and all its sub-handlers option
// descriptions.
//
option_descriptions repository_root::
options ()
{
- option_descriptions r (module::options ());
+ option_descriptions r (handler::options ());
append (r, package_search_->options ());
append (r, package_details_->options ());
append (r, package_version_details_->options ());
@@ -136,18 +178,19 @@ namespace brep
append (r, build_force_->options ());
append (r, build_log_->options ());
append (r, builds_->options ());
+ append (r, submit_->options ());
return r;
}
- // Initialize sub-modules and parse own configuration options.
+ // Initialize sub-handlers and parse own configuration options.
//
void repository_root::
init (const name_values& v)
{
- auto sub_init = [this, &v] (module& m, const char* name)
+ auto sub_init = [this, &v] (handler& m, const char* name)
{
- // Initialize sub-module. Intercept exception handling to add sub-module
- // attribution.
+ // Initialize sub-handler. Intercept exception handling to add
+ // sub-handler attribution.
//
try
{
@@ -167,7 +210,7 @@ namespace brep
}
};
- // Initialize sub-modules.
+ // Initialize sub-handlers.
//
sub_init (*package_search_, "package_search");
sub_init (*package_details_, "package_details");
@@ -178,17 +221,18 @@ namespace brep
sub_init (*build_force_, "build_force");
sub_init (*build_log_, "build_log");
sub_init (*builds_, "builds");
+ sub_init (*submit_, "submit");
// Parse own configuration options.
//
- module::init (
+ handler::init (
filter (v, convert (options::repository_root::description ())));
}
void repository_root::
init (scanner& s)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
options_ = make_shared<options::repository_root> (
s, unknown_mode::fail, unknown_mode::fail);
@@ -197,7 +241,7 @@ namespace brep
options_->root (dir_path ("/"));
// To use libbutl timestamp printing functions later on (specifically in
- // sub-modules, while handling requests).
+ // sub-handlers, while handling requests).
//
tzset ();
}
@@ -205,7 +249,7 @@ namespace brep
bool repository_root::
handle (request& rq, response& rs)
{
- MODULE_DIAG;
+ HANDLER_DIAG;
const dir_path& root (options_->root ());
@@ -215,24 +259,21 @@ namespace brep
const path& lpath (rpath.leaf (root));
- // Delegate the request handling to the selected sub-module. Intercept
- // exception handling to add sub-module attribution.
+ // Delegate the request handling to the selected sub-handler. Intercept
+ // exception handling to add sub-handler attribution.
//
auto handle = [&rq, &rs, this] (const char* nm, bool fn = false) -> bool
{
try
{
- // Delegate the handling straight away if the sub-module is not a
+ // Delegate the handling straight away if the sub-handler is not a
// function. Otherwise, cleanup the request not to confuse the
- // sub-module with the unknown parameter.
+ // sub-handler with the unknown parameter.
//
if (!fn)
return handler_->handle (rq, rs, *log_);
- name_values p (rq.parameters ());
- p.erase (p.begin ());
-
- request_proxy rp (rq, p);
+ request_proxy rp (rq);
return handler_->handle (rp, rs, *log_);
}
catch (const invalid_request&)
@@ -250,7 +291,7 @@ namespace brep
// to the client with the internal server error (500) code. By that
// reason it is valid to reduce all these types to a single one. Note
// that the server_error exception is handled internally by the
- // module::handle() function call.
+ // handler::handle() function call.
//
ostringstream os;
os << nm << ": " << e;
@@ -258,22 +299,23 @@ namespace brep
}
};
- // Note that while selecting the sub-module type for handling the request,
+ // Note that while selecting the sub-handler type for handling the request,
// we rely on the fact that the initial and all the subsequent function
// calls (that may take place after the retry exception is thrown) will
// end-up with the same type, and so using the single handler instance for
// all of these calls is safe. Note that the selection also sets up the
- // handling context (sub-module name and optionally the request proxy).
+ // handling context (sub-handler name and optionally the request proxy).
//
if (lpath.empty ())
{
// Dispatch request handling to the repository_details or the one of
- // build_* modules depending on the function name passed as a first HTTP
+ // build_* handlers depending on the function name passed as a first HTTP
// request parameter (example: cppget.org/?about). Dispatch to the
- // package_search module if the function name is unavailable (no
+ // package_search handler if the function name is unavailable (no
// parameters) or is not recognized.
//
- const name_values& params (rq.parameters ());
+ const name_values& params (rq.parameters (0 /* limit */,
+ true /* url_only */));
if (!params.empty ())
{
const string& fn (params.front ().name);
@@ -313,6 +355,13 @@ namespace brep
return handle ("builds", true);
}
+ else if (fn == "submit")
+ {
+ if (handler_ == nullptr)
+ handler_.reset (new submit (*submit_));
+
+ return handle ("submit", true);
+ }
}
if (handler_ == nullptr)
@@ -323,7 +372,7 @@ namespace brep
else
{
// Dispatch request handling to the package_details, the
- // package_version_details or the build_log module depending on the HTTP
+ // package_version_details or the build_log handler depending on the HTTP
// request URL path.
//
auto i (lpath.begin ());
@@ -380,7 +429,7 @@ namespace brep
void repository_root::
version ()
{
- MODULE_DIAG;
+ HANDLER_DIAG;
info << "module " << BREP_VERSION_ID
<< ", libbrep " << LIBBREP_VERSION_ID
diff --git a/mod/mod-repository-root.hxx b/mod/mod-repository-root.hxx
index 70840ae..74691ea 100644
--- a/mod/mod-repository-root.hxx
+++ b/mod/mod-repository-root.hxx
@@ -22,8 +22,9 @@ namespace brep
class build_force;
class build_log;
class builds;
+ class submit;
- class repository_root: public module
+ class repository_root: public handler
{
public:
repository_root ();
@@ -65,14 +66,15 @@ namespace brep
shared_ptr<build_force> build_force_;
shared_ptr<build_log> build_log_;
shared_ptr<builds> builds_;
+ shared_ptr<submit> submit_;
shared_ptr<options::repository_root> options_;
- // Sub-module the request is dispatched to. Initially is NULL. It is set
+ // Sub-handler the request is dispatched to. Initially is NULL. It is set
// by the first call to handle() to a deep copy of the selected exemplar.
// The subsequent calls of handle() (that may take place after the retry
// exception is thrown) will use the existing handler instance.
//
- unique_ptr<module> handler_;
+ unique_ptr<handler> handler_;
};
}
diff --git a/mod/mod-submit.cxx b/mod/mod-submit.cxx
new file mode 100644
index 0000000..ff5fa9d
--- /dev/null
+++ b/mod/mod-submit.cxx
@@ -0,0 +1,715 @@
+// file : mod/mod-submit.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <mod/mod-submit.hxx>
+
+#include <cstdlib> // strtoul()
+#include <istream>
+
+#include <libbutl/sha256.mxx>
+#include <libbutl/process.mxx>
+#include <libbutl/sendmail.mxx>
+#include <libbutl/fdstream.mxx>
+#include <libbutl/timestamp.mxx>
+#include <libbutl/filesystem.mxx>
+#include <libbutl/process-io.mxx> // operator<<(ostream, process_args)
+#include <libbutl/manifest-parser.mxx>
+#include <libbutl/manifest-serializer.mxx>
+
+#include <web/xhtml.hxx>
+#include <web/module.hxx>
+
+#include <mod/page.hxx>
+#include <mod/options.hxx>
+
+using namespace std;
+using namespace butl;
+using namespace web;
+using namespace brep::cli;
+
+brep::submit::
+submit (const submit& r)
+ : handler (r),
+ options_ (r.initialized_ ? r.options_ : nullptr),
+ form_ (r.initialized_ || r.form_ == nullptr
+ ? r.form_
+ : make_shared<xhtml::fragment> (*r.form_))
+{
+}
+
+void brep::submit::
+init (scanner& s)
+{
+ HANDLER_DIAG;
+
+ options_ = make_shared<options::submit> (
+ s, unknown_mode::fail, unknown_mode::fail);
+
+ // Verify that the submission handling is setup properly, if configured.
+ //
+ if (options_->submit_data_specified ())
+ {
+ // Verify that directories satisfy the requirements.
+ //
+ auto verify = [&fail] (const dir_path& d, const char* what)
+ {
+ if (d.relative ())
+ fail << what << " directory path must be absolute";
+
+ if (!dir_exists (d))
+ fail << what << " directory '" << d << "' does not exist";
+ };
+
+ verify (options_->submit_data (), "submit-data");
+ verify (options_->submit_temp (), "submit-temp");
+
+ // Parse XHTML5 form file, if configured.
+ //
+ if (options_->submit_form_specified ())
+ {
+ const path& submit_form (options_->submit_form ());
+
+ if (submit_form.relative ())
+ fail << "submit-form path must be absolute";
+
+ try
+ {
+ ifdstream is (submit_form);
+
+ form_ = make_shared<xhtml::fragment> (is.read_text (),
+ submit_form.string ());
+ }
+ catch (const xml::parsing& e)
+ {
+ fail << "unable to parse submit-form file: " << e;
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read submit-form file '" << submit_form << "': "
+ << e;
+ }
+ }
+
+ if (options_->submit_handler_specified () &&
+ options_->submit_handler ().relative ())
+ fail << "submit-handler path must be absolute";
+ }
+
+ if (options_->root ().empty ())
+ options_->root (dir_path ("/"));
+}
+
+bool brep::submit::
+handle (request& rq, response& rs)
+{
+ using namespace xhtml;
+
+ using parser = manifest_parser;
+ using parsing = manifest_parsing;
+ using serializer = manifest_serializer;
+ using serialization = manifest_serialization;
+
+ HANDLER_DIAG;
+
+ const dir_path& root (options_->root ());
+
+ // We will respond with the manifest to the submission protocol violations
+ // and with a plain text message on the internal errors. In the latter case
+ // we will always respond with the same neutral message for security reason,
+ // logging the error details. Note that descriptions of exceptions caught by
+ // the web server are returned to the client (see web/module.hxx for
+ // details), and we want to avoid this when there is a danger of exposing
+ // sensitive data.
+ //
+ // Also we will pass through exceptions thrown by the underlying API, unless
+ // we need to handle them or add details for the description, in which case
+ // we will fallback to one of the above mentioned response methods.
+ //
+ // Note that both respond_manifest() and respond_error() are normally called
+ // right before the end of the request handling. They both always return
+ // true to allow bailing out with a single line, for example:
+ //
+ // return respond_error (); // Request is handled with an error.
+ //
+ auto respond_manifest = [&rs] (status_code status,
+ const string& message,
+ const char* ref = nullptr) -> bool
+ {
+ serializer s (rs.content (status, "text/manifest;charset=utf-8"),
+ "response");
+
+ s.next ("", "1"); // Start of manifest.
+ s.next ("status", to_string (status));
+ s.next ("message", message);
+
+ if (ref != nullptr)
+ s.next ("reference", ref);
+
+ s.next ("", ""); // End of manifest.
+ return true;
+ };
+
+ auto respond_error = [&rs] (status_code status = 500) -> bool
+ {
+ rs.content (status, "text/plain;charset=utf-8") << "unable to handle "
+ << "submission" << endl;
+ return true;
+ };
+
+ // Check if the package submission functionality is enabled.
+ //
+ // Note that this is not a submission protocol violation but it feels right
+ // to respond with the manifest, to help the client a bit.
+ //
+ if (!options_->submit_data_specified ())
+ return respond_manifest (404, "submission disabled");
+
+ // Parse the request form data and verifying the submission size limit.
+ //
+ // Note that if it is exceeded, then there are parameters and this is the
+ // submission rather than the form request, and so we respond with the
+ // manifest.
+ //
+ try
+ {
+ rq.parameters (options_->submit_max_size ());
+ }
+ catch (const invalid_request& e)
+ {
+ if (e.status == 413) // Payload too large?
+ return respond_manifest (e.status, "submission size exceeds limit");
+
+ throw;
+ }
+
+ // The request parameters are now parsed and the limit doesn't really matter.
+ //
+ const name_values& rps (rq.parameters (0 /* limit */));
+
+ // If there is no request parameters then we respond with the submission
+ // form XHTML, if configured. Otherwise, will proceed as for the submission
+ // request and will fail (missing parameters).
+ //
+ if (rps.empty () && form_ != nullptr)
+ {
+ const string title ("Submit");
+
+ xml::serializer s (rs.content (), title);
+
+ s << HTML
+ << HEAD
+ << TITLE << title << ~TITLE
+ << CSS_LINKS (path ("submit.css"), root)
+ << ~HEAD
+ << BODY
+ << DIV_HEADER (root, options_->logo (), options_->menu ())
+ << DIV(ID="content") << *form_ << ~DIV
+ << ~BODY
+ << ~HTML;
+
+ return true;
+ }
+
+ // Verify the submission parameters we expect. The unknown ones will be
+ // serialized to the submission manifest.
+ //
+ params::submit params;
+
+ try
+ {
+ name_value_scanner s (rps);
+ params = params::submit (s, unknown_mode::skip, unknown_mode::skip);
+ }
+ catch (const cli::exception&)
+ {
+ return respond_manifest (400, "invalid parameter");
+ }
+
+ const string& archive (params.archive ());
+ const string& sha256sum (params.sha256sum ());
+
+ if (archive.empty ())
+ return respond_manifest (400, "package archive expected");
+
+ if (sha256sum.empty ())
+ return respond_manifest (400, "package archive checksum expected");
+
+ if (sha256sum.size () != 64)
+ return respond_manifest (400, "invalid package archive checksum");
+
+ // Verify that unknown parameter values satisfy the requirements (contain
+ // only ASCII printable characters plus '\r', '\n', and '\t').
+ //
+ // Actually, the expected ones must satisfy too, so check them as well.
+ //
+ auto printable = [] (const string& s) -> bool
+ {
+ for (char c: s)
+ {
+ if (!((c >= 0x20 && c <= 0x7E) || c == '\n' || c == '\r' || c == '\t'))
+ return false;
+ }
+ return true;
+ };
+
+ for (const name_value& nv: rps)
+ {
+ if (nv.value && !printable (*nv.value))
+ return respond_manifest (400, "invalid parameter " + nv.name);
+ }
+
+ // Check for a duplicate submission.
+ //
+ // Respond with the conflict (409) code if a duplicate is found.
+ //
+ string ac (sha256sum, 0, 12);
+ dir_path dd (options_->submit_data () / dir_path (ac));
+
+ if (dir_exists (dd))
+ return respond_manifest (409, "duplicate submission");
+
+ // Create the temporary submission directory.
+ //
+ dir_path td;
+
+ try
+ {
+ // Note that providing a meaningful prefix for temp_name() is not really
+ // required as the temporary directory is used by brep exclusively. However,
+ // using the abbreviated checksum can be helpful for troubleshooting.
+ //
+ td = dir_path (options_->submit_temp () /
+ dir_path (path::traits::temp_name (ac)));
+
+ // It's highly unlikely but still possible that the temporary directory
+ // already exists. This can only happen due to the unclean web server
+ // shutdown. Let's remove it and retry.
+ //
+ if (try_mkdir (td) == mkdir_status::already_exists)
+ {
+ try_rmdir_r (td);
+
+ if (try_mkdir (td) == mkdir_status::already_exists)
+ throw_generic_error (EEXIST);
+ }
+ }
+ catch (const invalid_path&)
+ {
+ return respond_manifest (400, "invalid package archive checksum");
+ }
+ catch (const system_error& e)
+ {
+ error << "unable to create directory '" << td << "': " << e;
+ return respond_error ();
+ }
+
+ auto_rmdir tdr (td);
+
+ // Save the package archive into the temporary directory and verify its
+ // checksum.
+ //
+ // Note that the archive file name can potentially contain directory path
+ // in the client's form (e.g., Windows), so let's strip it if that's the
+ // case.
+ //
+ path a;
+ path af;
+
+ try
+ {
+ size_t n (archive.find_last_of ("\\/"));
+ a = path (n != string::npos ? string (archive, n + 1) : archive);
+ af = td / a;
+ }
+ catch (const invalid_path&)
+ {
+ return respond_manifest (400, "invalid package archive name");
+ }
+
+ try
+ {
+ istream& is (rq.open_upload ("archive"));
+
+ // Note that istream::read() sets failbit if unable to read the requested
+ // number of bytes.
+ //
+ is.exceptions (istream::badbit);
+
+ sha256 sha;
+ char buf[8192];
+ ofdstream os (af, ios::binary);
+
+ while (!eof (is))
+ {
+ is.read (buf, sizeof (buf));
+
+ if (size_t n = is.gcount ())
+ {
+ sha.append (buf, n);
+ os.write (buf, n);
+ }
+ }
+
+ os.close ();
+
+ if (sha.string () != sha256sum)
+ return respond_manifest (400, "package archive checksum mismatch");
+ }
+ // Note that invalid_argument (thrown by open_upload() function call) can
+ // mean both no archive upload or multiple archive uploads.
+ //
+ catch (const invalid_argument&)
+ {
+ return respond_manifest (400, "package archive upload expected");
+ }
+ catch (const io_error& e)
+ {
+ error << "unable to write package archive '" << af << "': " << e;
+ return respond_error ();
+ }
+
+ // Serialize the submission request manifest to a stream. On the
+ // serialization error respond to the client with the manifest containing
+ // the bad request (400) code and return false, on the stream error pass
+ // through the io_error exception, otherwise return true.
+ //
+ timestamp ts (system_clock::now ());
+
+ auto rqm = [&a, &sha256sum, &ts, &rq, &rps, &respond_manifest]
+ (ostream& os) -> bool
+ {
+ try
+ {
+ serializer s (os, "request");
+
+ // Serialize the submission manifest header.
+ //
+ s.next ("", "1"); // Start of manifest.
+ s.next ("archive", a.string ());
+ s.next ("sha256sum", sha256sum);
+
+ s.next ("timestamp",
+ butl::to_string (ts,
+ "%Y-%m-%dT%H:%M:%SZ",
+ false /* special */,
+ false /* local */));
+
+ // Serialize the User-Agent HTTP header and the client IP address.
+ //
+ optional<string> ip;
+ optional<string> ua;
+ for (const name_value& h: rq.headers ())
+ {
+ if (casecmp (h.name, ":Client-IP") == 0)
+ ip = h.value;
+ else if (casecmp (h.name, "User-Agent") == 0)
+ ua = h.value;
+ }
+
+ if (ip)
+ s.next ("client-ip", *ip);
+
+ if (ua)
+ s.next ("user-agent", *ua);
+
+ // Serialize the request parameters.
+ //
+ // Note that the serializer constraints the parameter names (can't start
+ // with '#', can't contain ':' and the whitespaces, etc.).
+ //
+ for (const name_value& nv: rps)
+ {
+ const string& n (nv.name);
+ if (n != "archive" && n != "sha256sum")
+ s.next (n, nv.value ? *nv.value : "");
+ }
+
+ s.next ("", ""); // End of manifest.
+ return true;
+ }
+ catch (const serialization& e)
+ {
+ respond_manifest (400, string ("invalid parameter: ") + e.what ());
+ return false;
+ }
+ };
+
+ // Serialize the submission request manifest to the temporary submission
+ // directory.
+ //
+ path rqf (td / "request.manifest");
+
+ try
+ {
+ ofdstream os (rqf);
+ bool r (rqm (os));
+ os.close ();
+
+ if (!r)
+ return true; // The client is already responded with the manifest.
+ }
+ catch (const io_error& e)
+ {
+ error << "unable to write to '" << rqf << "': " << e;
+ return respond_error ();
+ }
+
+ // Make the temporary submission directory permanent.
+ //
+ // Respond with the conflict (409) code if a submission race is detected.
+ //
+ try
+ {
+ mvdir (td, dd);
+ }
+ catch (const system_error& e)
+ {
+ int ec (e.code ().value ());
+ if (ec == ENOTEMPTY || ec == EEXIST)
+ return respond_manifest (409, "duplicate submission");
+
+ error << "unable to rename directory '" << td << "' to '" << dd << "': "
+ << e;
+
+ return respond_error ();
+ }
+
+ // Given that the submission data is now successfully persisted we are no
+ // longer in charge of removing it, even in case of a subsequent error.
+ //
+ tdr.cancel ();
+
+ auto print_args = [&trace, this] (const char* args[], size_t n)
+ {
+ l2 ([&]{trace << process_args {args, n};});
+ };
+
+ // Run the submission handler, if specified, reading the result manifest
+ // from its stdout and caching it as a name/value pair list for later use
+ // (forwarding to the client, sending via email, etc.).
+ //
+ // Note that if the handler is configured then the cache can never be empty,
+ // containing at least the status value. Thus, an empty cache indicates that
+ // the handler is not configured.
+ //
+ status_code sc;
+ vector<manifest_name_value> rvs;
+
+ if (options_->submit_handler_specified ())
+ {
+ const path& handler (options_->submit_handler ());
+
+ for (;;) // Breakout loop.
+ try
+ {
+ fdpipe pipe (fdopen_pipe ()); // Can throw io_error.
+
+ // Redirect the diagnostics to the web server error log.
+ //
+ process pr (
+ process_start_callback (print_args,
+ 0 /* stdin */,
+ pipe /* stdout */,
+ 2 /* stderr */,
+ handler,
+ options_->submit_handler_argument (),
+ dd));
+ pipe.out.close ();
+
+ try
+ {
+ ifdstream is (move (pipe.in));
+
+ // Parse and verify the manifest. Obtain the HTTP status code (must go
+ // first) and cache it for the subsequent responding to the client.
+ //
+ parser p (is, "handler");
+ manifest_name_value nv (p.next ());
+
+ auto bad_name ([&p, &nv] (const string& d) {
+ throw parsing (p.name (), nv.name_line, nv.name_column, d);});
+
+ auto bad_value ([&p, &nv] (const string& d) {
+ throw parsing (p.name (), nv.value_line, nv.value_column, d);});
+
+ const string& n (nv.name);
+ const string& v (nv.value);
+
+ // Make sure this is the start and we support the version.
+ //
+ if (!n.empty ())
+ bad_name ("start of manifest expected");
+
+ if (v != "1")
+ bad_value ("unsupported format version");
+
+ // Cache start of manifest.
+ //
+ rvs.push_back (move (nv));
+
+ // Get and verify the HTTP status.
+ //
+ nv = p.next ();
+ if (n != "status")
+ bad_value ("no status specified");
+
+ char* e (nullptr);
+ unsigned long c (strtoul (v.c_str (), &e, 10)); // Can't throw.
+
+ assert (e != nullptr);
+
+ if (!(*e == '\0' && c >= 100 && c < 600))
+ bad_value ("invalid http status '" + v + "'");
+
+ // Cache the HTTP status.
+ //
+ sc = static_cast<status_code> (c);
+ rvs.push_back (move (nv));
+
+ // Cache the remaining name/value pairs.
+ //
+ for (nv = p.next (); !nv.empty (); nv = p.next ())
+ rvs.push_back (move (nv));
+
+ // Cache end of manifest.
+ //
+ rvs.push_back (move (nv));
+
+ is.close ();
+
+ if (pr.wait ())
+ break; // Get out of the breakout loop.
+
+ assert (pr.exit);
+ error << "process " << handler << " " << *pr.exit;
+
+ // Fall through.
+ }
+ catch (const parsing& e)
+ {
+ if (pr.wait ())
+ error << "unable to parse handler's output: " << e;
+
+ // Fall through.
+ }
+ catch (const io_error& e)
+ {
+ if (pr.wait ())
+ error << "unable to read handler's output: " << e;
+
+ // Fall through.
+ }
+
+ return respond_error ();
+ }
+ // Handle process_error and io_error (both derive from system_error).
+ //
+ catch (const system_error& e)
+ {
+ error << "unable to execute '" << handler << "': " << e;
+ return respond_error ();
+ }
+ }
+
+ // Serialize the submission result manifest to a stream. On the
+ // serialization error log the error description and return false, on the
+ // stream error pass through the io_error exception, otherwise return true.
+ //
+ auto rsm = [&rvs, &error] (ostream& os) -> bool
+ {
+ assert (!rvs.empty ());
+
+ try
+ {
+ serializer s (os, "result");
+ for (const manifest_name_value& nv: rvs)
+ s.next (nv.name, nv.value);
+
+ return true;
+ }
+ catch (const serialization& e)
+ {
+ error << "unable to serialize handler's output: " << e;
+ return false;
+ }
+ };
+
+ // Save the result manifest, if generated, into the submission directory
+ // if it still exists (note that the handler could move or remove it).
+ //
+ path rsf (dd / "result.manifest");
+
+ if (!rvs.empty () && dir_exists (dd))
+ try
+ {
+ ofdstream os (rsf);
+ bool r (rsm (os));
+ os.close ();
+
+ if (!r)
+ return respond_error (); // The error description is already logged.
+ }
+ catch (const io_error& e)
+ {
+ error << "unable to write to '" << rsf << "': " << e;
+ return respond_error ();
+ }
+
+ // Send email, if configured.
+ //
+ // Note that we don't consider the email sending failure to be a submission
+ // failure as the submission data is successfully persisted and the handler
+ // is successfully executed, if configured. One can argue that email can be
+ // essential for the submission processing and missing it would result in
+ // the incomplete submission. In this case it's natural to assume that the
+ // web server error log is monitored and the email sending failure will be
+ // noticed.
+ //
+ if (options_->submit_email_specified ())
+ try
+ {
+ // Redirect the diagnostics to the web server error log.
+ //
+ sendmail sm (print_args,
+ 2 /* stderr */,
+ options_->email (),
+ "new package submission " + a.string () + " (" + ac + ")",
+ {options_->submit_email ()});
+
+ // Write the submission request manifest.
+ //
+ bool r (rqm (sm.out));
+ assert (r); // The serialization succeeded once, so can't fail now.
+
+ // Write the submission result manifest, if present.
+ //
+ if (!rvs.empty ())
+ {
+ sm.out << "\n\n";
+
+ rsm (sm.out); // We don't care about the result (see above).
+ }
+
+ sm.out.close ();
+
+ if (!sm.wait ())
+ error << "sendmail " << *sm.exit;
+ }
+ // Handle process_error and io_error (both derive from system_error).
+ //
+ catch (const system_error& e)
+ {
+ error << "sendmail error: " << e;
+ }
+
+ // Respond with implied result manifest if the handler is not configured.
+ //
+ if (rvs.empty ())
+ return respond_manifest (200, "submission queued", ac.c_str ());
+
+ if (!rsm (rs.content (sc, "text/manifest;charset=utf-8")))
+ return respond_error (); // The error description is already logged.
+
+ return true;
+}
diff --git a/mod/mod-submit.hxx b/mod/mod-submit.hxx
new file mode 100644
index 0000000..ea83e03
--- /dev/null
+++ b/mod/mod-submit.hxx
@@ -0,0 +1,45 @@
+// file : mod/mod-submit.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef MOD_MOD_SUBMIT_HXX
+#define MOD_MOD_SUBMIT_HXX
+
+#include <web/xhtml-fragment.hxx>
+
+#include <libbrep/types.hxx>
+#include <libbrep/utility.hxx>
+
+#include <mod/module.hxx>
+#include <mod/options.hxx>
+
+namespace brep
+{
+ class submit: public handler
+ {
+ public:
+ submit () = default;
+
+ // Create a shallow copy (handling instance) if initialized and a deep
+ // copy (context exemplar) otherwise.
+ //
+ explicit
+ submit (const submit&);
+
+ virtual bool
+ handle (request&, response&);
+
+ virtual const cli::options&
+ cli_options () const {return options::submit::description ();}
+
+ private:
+ virtual void
+ init (cli::scanner&);
+
+ private:
+ shared_ptr<options::submit> options_;
+ shared_ptr<web::xhtml::fragment> form_;
+ };
+}
+
+#endif // MOD_MOD_SUBMIT_HXX
diff --git a/mod/module.cxx b/mod/module.cxx
index 8a3f78b..82fc312 100644
--- a/mod/module.cxx
+++ b/mod/module.cxx
@@ -21,9 +21,9 @@ using namespace placeholders; // For std::bind's _1, etc.
namespace brep
{
- // module
+ // handler
//
- bool module::
+ bool handler::
handle (request& rq, response& rs, log& l)
{
log_ = &l;
@@ -74,7 +74,7 @@ namespace brep
return true;
}
- option_descriptions module::
+ option_descriptions handler::
convert (const cli::options& o)
{
option_descriptions r;
@@ -82,7 +82,7 @@ namespace brep
return r;
}
- void module::
+ void handler::
append (option_descriptions& dst, const cli::options& src)
{
for (const auto& o: src)
@@ -99,7 +99,7 @@ namespace brep
}
}
- void module::
+ void handler::
append (option_descriptions& dst, const option_descriptions& src)
{
for (const auto& o: src)
@@ -109,7 +109,7 @@ namespace brep
}
}
- name_values module::
+ name_values handler::
filter (const name_values& v, const option_descriptions& d)
{
name_values r;
@@ -123,20 +123,20 @@ namespace brep
}
// Convert CLI option descriptions to the general interface of option
- // descriptions, extend with brep::module own option descriptions.
+ // descriptions, extend with brep::handler own option descriptions.
//
- option_descriptions module::
+ option_descriptions handler::
options ()
{
option_descriptions r ({{"conf", true}});
- append (r, options::module::description ());
+ append (r, options::handler::description ());
append (r, cli_options ());
return r;
}
// Expand option list parsing configuration files.
//
- name_values module::
+ name_values handler::
expand_options (const name_values& v)
{
using namespace cli;
@@ -175,14 +175,14 @@ namespace brep
}
// Parse options with a cli-generated scanner. Options verb and conf are
- // recognized by brep::module::init while others to be interpreted by the
+ // recognized by brep::handler::init while others to be interpreted by the
// derived init(). If there is an option which can not be interpreted
- // neither by brep::module nor by the derived class, then the web server
+ // neither by brep::handler nor by the derived class, then the web server
// is terminated with a corresponding error message being logged. Though
// this should not happen if the options() function returned the correct
// set of options.
//
- void module::
+ void handler::
init (const name_values& options, log& log)
{
assert (!initialized_);
@@ -193,18 +193,18 @@ namespace brep
{
name_values opts (expand_options (options));
- // Read module implementation configuration.
+ // Read handler implementation configuration.
//
init (opts);
- // Read brep::module configuration.
+ // Read brep::handler configuration.
//
static option_descriptions od (
- convert (options::module::description ()));
+ convert (options::handler::description ()));
name_values mo (filter (opts, od));
name_value_scanner s (mo);
- options::module o (s, cli::unknown_mode::fail, cli::unknown_mode::fail);
+ options::handler o (s, cli::unknown_mode::fail, cli::unknown_mode::fail);
verb_ = o.verbosity ();
initialized_ = true;
@@ -222,21 +222,21 @@ namespace brep
}
}
- void module::
+ void handler::
init (const name_values& options)
{
name_value_scanner s (options);
init (s);
- assert (!s.more ()); // Module didn't handle its options.
+ assert (!s.more ()); // Handler didn't handle its options.
}
- module::
- module (): log_writer_ (bind (&module::log_write, this, _1)) {}
+ handler::
+ handler (): log_writer_ (bind (&handler::log_write, this, _1)) {}
// Custom copy constructor is required to initialize log_writer_ properly.
//
- module::
- module (const module& m): module ()
+ handler::
+ handler (const handler& m): handler ()
{
verb_ = m.verb_;
initialized_ = m.initialized_;
@@ -250,7 +250,7 @@ namespace brep
// virtual std::string (* (* brep::search::func(std::string (* (*)(char))(int)
// ,std::string (* (*)(wchar_t))(int)) const)(int, int))(int)
//
- string module::
+ string handler::
func_name (const char* pretty_name)
{
const char* e (strchr (pretty_name, ')'));
@@ -293,10 +293,10 @@ namespace brep
}
}
- throw invalid_argument ("::brep::module::func_name");
+ throw invalid_argument ("::brep::handler::func_name");
}
- void module::
+ void handler::
log_write (const diag_data& d) const
{
if (log_ == nullptr)
@@ -313,7 +313,7 @@ namespace brep
//
// Use APLOG_INFO (as opposed to APLOG_TRACE1) as a mapping for
// severity::trace. "LogLevel trace1" configuration directive switches
- // on the avalanche of log messages from various modules. Would be good
+ // on the avalanche of log messages from various handlers. Would be good
// to avoid wading through them.
//
static int s[] = {APLOG_ERR, APLOG_WARNING, APLOG_INFO, APLOG_INFO};
@@ -341,16 +341,16 @@ namespace brep
}
}
- void module::
+ void handler::
version (log& l)
{
log_ = &l;
version ();
}
- // module::name_value_scanner
+ // handler::name_value_scanner
//
- module::name_value_scanner::
+ handler::name_value_scanner::
name_value_scanner (const name_values& nv) noexcept
: name_values_ (nv),
i_ (nv.begin ()),
@@ -358,13 +358,13 @@ namespace brep
{
}
- bool module::name_value_scanner::
+ bool handler::name_value_scanner::
more ()
{
return i_ != name_values_.end ();
}
- const char* module::name_value_scanner::
+ const char* handler::name_value_scanner::
peek ()
{
if (i_ != name_values_.end ())
@@ -373,7 +373,7 @@ namespace brep
throw cli::eos_reached ();
}
- const char* module::name_value_scanner::
+ const char* handler::name_value_scanner::
next ()
{
if (i_ != name_values_.end ())
@@ -386,7 +386,7 @@ namespace brep
throw cli::eos_reached ();
}
- void module::name_value_scanner::
+ void handler::name_value_scanner::
skip ()
{
if (i_ != name_values_.end ())
diff --git a/mod/module.hxx b/mod/module.hxx
index f549892..127cdab 100644
--- a/mod/module.hxx
+++ b/mod/module.hxx
@@ -19,7 +19,7 @@ namespace brep
//
// @@ Maybe doing using namespace is the right way to handle this.
// There will, however, most likely be a conflict between
- // web::module and our module. Or maybe not, need to try.
+ // web::handler and our handler. Or maybe not, need to try.
//
using web::status_code;
using web::invalid_request;
@@ -33,7 +33,7 @@ namespace brep
// This exception indicated a server error (5XX). In particular,
// it is thrown by the fail diagnostics stream and is caught by the
- // module implementation where it is both logged as an error and
+ // handler implementation where it is both logged as an error and
// returned to the user with the 5XX status code.
//
struct server_error
@@ -43,15 +43,15 @@ namespace brep
server_error (diag_data&& d): data (move (d)) {}
};
- // Every module member function that needs to produce any diagnostics
+ // Every handler member function that needs to produce any diagnostics
// shall begin with:
//
- // MODULE_DIAG;
+ // HANDLER_DIAG;
//
// This will instantiate the fail, error, warn, info, and trace
// diagnostics streams with the function's name.
//
-#define MODULE_DIAG \
+#define HANDLER_DIAG \
const fail_mark<server_error> fail (__PRETTY_FUNCTION__); \
const basic_mark error (severity::error, \
this->log_writer_, \
@@ -66,9 +66,9 @@ namespace brep
this->log_writer_, \
__PRETTY_FUNCTION__)
- // Adaptation of the web::module to our needs.
+ // Adaptation of the web::handler to our needs.
//
- class module: public web::module
+ class handler: public web::handler
{
// Diagnostics.
//
@@ -87,15 +87,15 @@ namespace brep
template <class F> void l1 (const F& f) const {if (verb_ >= 1) f ();}
template <class F> void l2 (const F& f) const {if (verb_ >= 2) f ();}
- // Set to true when the module is successfully initialized.
+ // Set to true when the handler is successfully initialized.
//
bool initialized_ {false};
// Implementation details.
//
protected:
- module ();
- module (const module& );
+ handler ();
+ handler (const handler& );
static name_values
filter (const name_values&, const option_descriptions&);
@@ -109,7 +109,7 @@ namespace brep
static void
append (option_descriptions& dst, const option_descriptions& src);
- // Can be used by module implementation to parse HTTP request parameters.
+ // Can be used by handler implementation to parse HTTP request parameters.
//
class name_value_scanner: public cli::scanner
{
@@ -142,7 +142,7 @@ namespace brep
init (cli::scanner&) = 0;
// Can be overriden by custom request dispatcher to initialize
- // sub-modules.
+ // sub-handlers.
//
virtual void
init (const name_values&);
@@ -156,12 +156,12 @@ namespace brep
virtual bool
handle (request&, response&, log&);
- // web::module interface.
+ // web::handler interface.
//
public:
// Custom request dispatcher can aggregate its own option descriptions
- // with sub-modules option descriptions. In this case it should still call
- // the base implementation in order to include the brep::module's options.
+ // with sub-handlers option descriptions. In this case it should still call
+ // the base implementation in order to include the brep::handler's options.
//
virtual option_descriptions
options ();
@@ -170,7 +170,7 @@ namespace brep
virtual void
version (log&);
- // Can be overriden by the module implementation to log version, etc.
+ // Can be overriden by the handler implementation to log version, etc.
//
virtual void
version () {}
diff --git a/mod/options.cli b/mod/options.cli
index e6b0840..97453a7 100644
--- a/mod/options.cli
+++ b/mod/options.cli
@@ -10,13 +10,13 @@ include <mod/options-types.hxx>;
namespace brep
{
- // Web module configuration options.
+ // Web handler configuration options.
//
namespace options
{
// Option groups.
//
- class module
+ class handler
{
string email
{
@@ -307,9 +307,9 @@ namespace brep
}
};
- // Module options.
+ // Handler options.
//
- class package_search: search, package_db, page, module
+ class package_search: search, package_db, page, handler
{
string search-title = "Packages"
{
@@ -319,22 +319,22 @@ namespace brep
}
};
- class package_details: package, search, package_db, page, module
+ class package_details: package, search, package_db, page, handler
{
};
class package_version_details: package, package_db,
build, build_db,
page,
- module
+ handler
{
};
- class repository_details: package_db, page, module
+ class repository_details: package_db, page, handler
{
};
- class build_task: build, package_db, build_db, module
+ class build_task: build, package_db, build_db, handler
{
size_t build-task-request-max-size = 102400
{
@@ -353,7 +353,7 @@ namespace brep
}
};
- class build_result: build, package_db, build_db, module
+ class build_result: build, package_db, build_db, handler
{
size_t build-result-request-max-size = 10240000
{
@@ -365,15 +365,15 @@ namespace brep
}
};
- class build_log: build, package_db, build_db, module
+ class build_log: build, package_db, build_db, handler
{
};
- class build_force: build, package_db, build_db, module
+ class build_force: build, package_db, build_db, handler
{
};
- class builds: build, package_db, build_db, page, module
+ class builds: build, package_db, build_db, page, handler
{
uint16_t build-configurations = 10
{
@@ -388,7 +388,7 @@ namespace brep
}
};
- class submit: page, module
+ class submit: page, handler
{
dir_path submit-data
{
@@ -411,8 +411,8 @@ namespace brep
if the package submission functionality is enabled.
Note that this directory must be on the same filesystem and satisfy
- the same requirements as \cb{submit-data}. Its contents are
- automatically cleaned up on each web server startup."
+ the same requirements as \cb{submit-data}. It is also the user's
+ responsibility to clean it up after an unclean web server shutdown."
}
size_t submit-max-size = 10485760
@@ -460,12 +460,12 @@ namespace brep
}
};
- class repository_root: module
+ class repository_root: handler
{
};
}
- // Web module HTTP request parameters.
+ // Web handler HTTP request parameters.
//
namespace params
{
diff --git a/mod/page.cxx b/mod/page.cxx
index 94e4f4f..614bf79 100644
--- a/mod/page.cxx
+++ b/mod/page.cxx
@@ -25,6 +25,10 @@ using namespace xml;
using namespace web;
using namespace web::xhtml;
+// Note that in HTML5 the boolean attribute absence represents false value,
+// true otherwise. If it is present then the value must be empty or
+// case-insensitively match the attribute's name.
+//
namespace brep
{
// CSS_LINKS
@@ -90,8 +94,7 @@ namespace brep
<< TBODY
<< TR
<< TD(ID="search-txt")
- << *INPUT(TYPE="search", NAME="q", VALUE=query_,
- AUTOFOCUS="autofocus")
+ << *INPUT(TYPE="search", NAME="q", VALUE=query_, AUTOFOCUS="")
<< ~TD
<< TD(ID="search-btn")
<< *INPUT(TYPE="submit", VALUE="Search")
@@ -141,7 +144,7 @@ namespace brep
s << PLACEHOLDER(placeholder_);
if (autofocus_)
- s << AUTOFOCUS("autofocus");
+ s << AUTOFOCUS("");
s << ~INPUT
<< ~TD