// file : web/apache/service.cxx -*- C++ -*- // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #include <web/apache/service> #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 <butl/optional> #include <web/module> #include <web/apache/log> using namespace std; 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 (), reinterpret_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 module is allowed to handle a request in the specific // configuration scope. The module exemplar will be created (and // initialized) only for configuration contexts that have // 'SetHandler <mod_name>' in effect for the corresponding scope. // *d++ = { "SetHandler", reinterpret_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; } } }