From 21033565488f6c63b4c40962cccfdc8b6ca32b2a Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sat, 7 Jul 2018 19:09:53 +0300 Subject: Add support for package submission --- mod/database-module.cxx | 6 +- mod/database-module.hxx | 8 +- mod/mod-build-force.cxx | 6 +- mod/mod-build-log.cxx | 6 +- mod/mod-build-result.cxx | 13 +- mod/mod-build-task.cxx | 13 +- mod/mod-builds.cxx | 6 +- mod/mod-package-details.cxx | 6 +- mod/mod-package-search.cxx | 11 +- mod/mod-package-version-details.cxx | 6 +- mod/mod-repository-details.cxx | 6 +- mod/mod-repository-root.cxx | 123 +++++-- mod/mod-repository-root.hxx | 8 +- mod/mod-submit.cxx | 715 ++++++++++++++++++++++++++++++++++++ mod/mod-submit.hxx | 45 +++ mod/module.cxx | 68 ++-- mod/module.hxx | 32 +- mod/options.cli | 34 +- mod/page.cxx | 9 +- 19 files changed, 976 insertions(+), 145 deletions(-) create mode 100644 mod/mod-submit.cxx create mode 100644 mod/mod-submit.hxx (limited to 'mod') 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 ( 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 ( 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 ( 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 ( 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 ( 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 ( 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 ( 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 ( 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 ( 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 #include +#include #include #include #include @@ -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 parameters_; + bool url_only_parameters_; // Meaningless if parameters_ is not present. }; // repository_root @@ -65,15 +102,16 @@ namespace brep build_result_ (make_shared ()), build_force_ (make_shared ()), build_log_ (make_shared ()), - builds_ (make_shared ()) + builds_ (make_shared ()), + submit_ (make_shared ()) { } 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 (*r.builds_)), + submit_ ( + r.initialized_ + ? r.submit_ + : make_shared (*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 ( 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_; shared_ptr build_log_; shared_ptr builds_; + shared_ptr submit_; shared_ptr 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 handler_; + unique_ptr 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 + +#include // strtoul() +#include + +#include +#include +#include +#include +#include +#include +#include // operator<<(ostream, process_args) +#include +#include + +#include +#include + +#include +#include + +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 (*r.form_)) +{ +} + +void brep::submit:: +init (scanner& s) +{ + HANDLER_DIAG; + + options_ = make_shared ( + 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 (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 ip; + optional 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 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 (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 + +#include +#include + +#include +#include + +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_; + shared_ptr 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 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 void l1 (const F& f) const {if (verb_ >= 1) f ();} template 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 ; 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 -- cgit v1.1