aboutsummaryrefslogtreecommitdiff
path: root/web/server/apache/service.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'web/server/apache/service.cxx')
-rw-r--r--web/server/apache/service.cxx268
1 files changed, 268 insertions, 0 deletions
diff --git a/web/server/apache/service.cxx b/web/server/apache/service.cxx
new file mode 100644
index 0000000..9fb23da
--- /dev/null
+++ b/web/server/apache/service.cxx
@@ -0,0 +1,268 @@
+// file : web/server/apache/service.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <web/server/apache/service.hxx>
+
+#include <apr_pools.h> // apr_palloc()
+
+#include <httpd.h> // server_rec
+#include <http_config.h> // command_rec, cmd_*, ap_get_module_config()
+
+#include <memory> // unique_ptr
+#include <string>
+#include <cassert>
+#include <utility> // move()
+#include <cstring> // strlen(), strcmp()
+#include <exception>
+
+#include <libbutl/utility.mxx> // function_cast()
+#include <libbutl/optional.mxx>
+
+#include <web/server/module.hxx>
+#include <web/server/apache/log.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace web
+{
+ namespace apache
+ {
+ void service::
+ init_directives ()
+ {
+ assert (cmds == nullptr);
+
+ // Fill apache module directive definitions. Directives share common
+ // name space in apache configuration file, so to prevent name clash
+ // have to form directive name as a combination of module and option
+ // names: <module name>-<option name>. This why for option bar of module
+ // foo the corresponding directive will appear in apache configuration
+ // file as foo-bar.
+ //
+ const option_descriptions& od (exemplar_.options ());
+ unique_ptr<command_rec[]> directives (new command_rec[od.size () + 2]);
+ command_rec* d (directives.get ());
+
+ for (const auto& o: od)
+ {
+ auto i (
+ option_descriptions_.emplace (name_ + "-" + o.first, o.second));
+ assert (i.second);
+
+ *d++ =
+ {
+ i.first->first.c_str (),
+ function_cast<cmd_func> (parse_option),
+ this,
+
+ // Allow directives in both server and directory configuration
+ // scopes.
+ //
+ RSRC_CONF | ACCESS_CONF,
+
+ // Move away from TAKE1 to be able to handle empty string and
+ // no-value.
+ //
+ RAW_ARGS,
+
+ nullptr
+ };
+ }
+
+ // Track if the handler is allowed to handle a request in the specific
+ // configuration scope. The handler exemplar will be created (and
+ // initialized) only for configuration contexts that have
+ // 'SetHandler <mod_name>' in effect for the corresponding scope.
+ //
+ *d++ =
+ {
+ "SetHandler",
+ function_cast<cmd_func> (parse_option),
+ this,
+ RSRC_CONF | ACCESS_CONF,
+ RAW_ARGS,
+ nullptr
+ };
+
+ *d = {nullptr, nullptr, nullptr, 0, RAW_ARGS, nullptr};
+ cmds = directives.release ();
+ }
+
+ void* service::
+ create_server_context (apr_pool_t* pool, server_rec*) noexcept
+ {
+ // Create the object using the configuration memory pool provided by the
+ // Apache API. The lifetime of the object is equal to the lifetime of
+ // the pool.
+ //
+ void* p (apr_palloc (pool, sizeof (context)));
+ assert (p != nullptr);
+ return new (p) context ();
+ }
+
+ void* service::
+ create_dir_context (apr_pool_t* pool, char* dir) noexcept
+ {
+ // Create the object using the configuration memory pool provided by the
+ // Apache API. The lifetime of the object is equal to the lifetime of
+ // the pool.
+ //
+ void* p (apr_palloc (pool, sizeof (context)));
+ assert (p != nullptr);
+
+ // For the user-defined directory configuration context dir is the path
+ // of the corresponding directive. For the special server directory
+ // invented by Apache for server scope directives, dir is NULL.
+ //
+ return new (p) context (dir == nullptr);
+ }
+
+ const char* service::
+ parse_option (cmd_parms* parms, void* conf, const char* args) noexcept
+ {
+ service& srv (*reinterpret_cast<service*> (parms->cmd->cmd_data));
+
+ if (srv.options_parsed_)
+ // Apache have started the second pass of its messy initialization
+ // cycle (more details at http://wiki.apache.org/httpd/ModuleLife).
+ // This time we are parsing for real. Cleanup the existing config, and
+ // start building the new one.
+ //
+ srv.clear_config ();
+
+ // 'args' is an optionally double-quoted string. It uses double quotes
+ // to distinguish empty string from no-value case.
+ //
+ assert (args != nullptr);
+
+ optional<string> value;
+ if (auto l = strlen (args))
+ value = l >= 2 && args[0] == '"' && args[l - 1] == '"'
+ ? string (args + 1, l - 2)
+ : args;
+
+ // Determine the directory and server configuration contexts for the
+ // option.
+ //
+ context* dir_context (context_cast (conf));
+ assert (dir_context != nullptr);
+
+ server_rec* server (parms->server);
+ assert (server != nullptr);
+ assert (server->module_config != nullptr);
+
+ context* srv_context (
+ context_cast (ap_get_module_config (server->module_config, &srv)));
+
+ assert (srv_context != nullptr);
+
+ // Associate the directory configuration context with the enclosing
+ // server configuration context.
+ //
+ context*& s (dir_context->server);
+ if (s == nullptr)
+ s = srv_context;
+ else
+ assert (s == srv_context);
+
+ // If the option appears in the special directory configuration context,
+ // add it to the enclosing server context instead. This way it will be
+ // possible to complement all server-enclosed contexts (including this
+ // special one) with the server scope options.
+ //
+ context* c (dir_context->special ? srv_context : dir_context);
+
+ if (dir_context->special)
+ //
+ // Make sure the special directory context is also in the option lists
+ // map. Later the context will be populated with an enclosing server
+ // context options.
+ //
+ srv.options_.emplace (dir_context, name_values ());
+
+ const char* name (parms->cmd->name);
+ if (strcmp (name, "SetHandler") == 0)
+ {
+ // Keep track of a request handling allowability.
+ //
+ srv.options_.emplace (c, name_values ()).first->first->handling =
+ value && *value == srv.name_
+ ? request_handling::allowed
+ : request_handling::disallowed;
+
+ return 0;
+ }
+
+ return srv.add_option (c, name, move (value));
+ }
+
+ const char* service::
+ add_option (context* ctx, const char* name, optional<string> value)
+ {
+ auto i (option_descriptions_.find (name));
+ assert (i != option_descriptions_.end ());
+
+ // Check that option value presense is expected.
+ //
+ if (i->second != static_cast<bool> (value))
+ return value ? "unexpected value" : "value expected";
+
+ options_[ctx].emplace_back (name + name_.length () + 1, move (value));
+ return 0;
+ }
+
+ void service::
+ complement (context* enclosed, context* enclosing)
+ {
+ auto i (options_.find (enclosing));
+
+ // The enclosing context may have no options. It can be the context of a
+ // server that has no configuration directives in it's immediate scope,
+ // but has ones in it's enclosed scope (directory or virtual server).
+ //
+ if (i != options_.end ())
+ {
+ const name_values& src (i->second);
+ name_values& dest (options_[enclosed]);
+ dest.insert (dest.begin (), src.begin (), src.end ());
+ }
+
+ if (enclosed->handling == request_handling::inherit)
+ enclosed->handling = enclosing->handling;
+ }
+
+ void service::
+ finalize_config (server_rec* s)
+ {
+ if (!version_logged_)
+ {
+ log l (s, this);
+ exemplar_.version (l);
+ version_logged_ = true;
+ }
+
+ // Complement directory configuration contexts with options of the
+ // enclosing server configuration context. By this time virtual server
+ // contexts are already complemented with the main server configuration
+ // context options as a result of the merge_server_context() calls.
+ //
+ for (const auto& o: options_)
+ {
+ // Is a directory configuration context.
+ //
+ if (o.first->server != nullptr)
+ complement (o.first, o.first->server);
+ }
+
+ options_parsed_ = true;
+ }
+
+ void service::
+ clear_config ()
+ {
+ options_.clear ();
+ options_parsed_ = false;
+ }
+ }
+}