Roxen.git / server / base_server / module.pike

version» Context lines:

Roxen.git/server/base_server/module.pike:1:   // This file is part of Roxen WebServer.   // Copyright © 1996 - 2009, Roxen IS. - // $Id: module.pike,v 1.245 2012/01/23 12:50:52 grubba Exp $ + // $Id$      #include <module_constants.h>   #include <module.h>   #include <request_trace.h>      constant __pragma_save_parent__ = 1;      // Tell Pike.count_memory this is global.   constant pike_cycle_depth = 0;   
Roxen.git/server/base_server/module.pike:38:    [_my_configuration, _module_local_identifier] = init_info;    return _my_configuration->name + "/" + _module_local_identifier;    }   #ifdef DEBUG    else    error ("Got invalid bootstrap info for module: %O\n", init_info);   #endif    }();   protected mapping _api_functions = ([]);    + class ModuleJSONLogger { +  inherit Logger.BaseJSONLogger; +  +  void create(object parent_config) { +  string name = combine_path_unix(parent_config->json_logger->logger_name, +  module_local_id()); +  ::create(name, UNDEFINED, parent_config->json_logger); +  } + } +  + // Module local JSON logger + private ModuleJSONLogger json_logger; +    string|array(string) module_creator;   string module_url;   RXML.TagSet module_tag_set;      /* These functions exist in here because otherwise the messages in the    * event log do not always end up in the correct module/configuration.    * And the reason for that is that if the messages are logged from    * subclasses in the module, the DWIM in roxenlib.pike cannot see that    * they are logged from a module. This solution is not really all that    * beautiful, but it works. :-)
Roxen.git/server/base_server/module.pike:59:   void report_fatal(sprintf_format fmt, sprintf_args ... args)    { predef::report_fatal(fmt, @args); }   void report_error(sprintf_format fmt, sprintf_args ... args)    { predef::report_error(fmt, @args); }   void report_warning(sprintf_format fmt, sprintf_args ... args)    { predef::report_warning(fmt, @args); }   void report_notice(sprintf_format fmt, sprintf_args ... args)    { predef::report_notice(fmt, @args); }   void report_debug(sprintf_format fmt, sprintf_args ... args)    { predef::report_debug(fmt, @args); } + void report_warning_sparsely (sprintf_format fmt, sprintf_args ... args) +  {predef::report_warning_sparsely (fmt, @args);} + void report_error_sparsely (sprintf_format fmt, sprintf_args ... args) +  {predef::report_error_sparsely (fmt, @args);}      void log_event (string facility, string action, string resource,    void|mapping(string:mixed) info)   //! Log an event. See @[Configuration.log_event] for details.   //!   //! @[facility] may be zero. The local module identifier as returned   //! by @[module_local_id] is used as facility in that case.   {    _my_configuration->log_event (facility || _module_local_identifier,    action, resource, info);   }    -  + void json_log_trace(string|mapping log_msg) { json_log_with_level(log_msg, Logger.BaseJSONLogger.TRACE); } + void json_log_debug(string|mapping log_msg) { json_log_with_level(log_msg, Logger.BaseJSONLogger.DBG); } + void json_log_info (string|mapping log_msg) { json_log_with_level(log_msg, Logger.BaseJSONLogger.INFO); } + void json_log_warn (string|mapping log_msg) { json_log_with_level(log_msg, Logger.BaseJSONLogger.WARN); } + void json_log_error(string|mapping log_msg) { json_log_with_level(log_msg, Logger.BaseJSONLogger.ERROR); } + void json_log_fatal(string|mapping log_msg) { json_log_with_level(log_msg, Logger.BaseJSONLogger.FATAL); } +  + // Helper method to force a specific logging level + void json_log_with_level(string|mapping log_msg, int level) { +  if (stringp(log_msg)) { +  log_msg = ([ +  "msg" : log_msg, +  ]); +  } +  log_msg->level = level; +  json_log(log_msg); + } +  + // Log a message more or less verbatim via the JSON logger infrastructure + void json_log(string|mapping log_msg) { +  if (stringp(log_msg)) { +  log_msg = ([ +  "msg" : log_msg, +  ]); +  } +  +  if (json_logger && functionp(json_logger->log)) { +  json_logger->log(log_msg); +  } + } +    string module_identifier()   //! Returns a string that uniquely identifies this module instance   //! within the server. The identifier is the same as   //! @[Roxen.get_module] and @[Roxen.get_modname] handles.   {    return _module_identifier;   }      string module_local_id()   //! Returns a string that uniquely identifies this module instance
Roxen.git/server/base_server/module.pike:196:   //! belongs to.   {    return _my_configuration;   }      final void set_configuration(Configuration c)   {    if(_my_configuration && _my_configuration != c)    error("set_configuration() called twice.\n");    _my_configuration = c; +  +  // if configuration changes, we should reinitialize our JSON logger too! +  json_logger = ModuleJSONLogger(_my_configuration);   }      void set_module_creator(string|array(string) c)   //! Set the name and optionally email address of the author of the   //! module. Names on the format "author name <author_email>" will   //! end up as links on the module's information page in the admin   //! interface. In the case of multiple authors, an array of such   //! strings can be passed.   {    module_creator = c;
Roxen.git/server/base_server/module.pike:364:    if(has_value(uri->host, "*") || has_value(uri->host, "?"))    if(glob(uri->host, hostname))    uri->host = hostname;    else {    if(!candidate_uri) {    candidate_uri = uri;    candidate_uri->host = hostname;    }    continue;    } -  return (string)uri + loc[1..]; +  uri->path += loc[1..]; +  return (string)uri;    }    if(candidate_uri) {    report_warning("Warning: Could not find any suitable ports, continuing anyway. "    "Please make sure that your Primary Server URL matches "    "at least one port. Primary Server URL: %O, URLs: %s.\n",    world_url, short_array(urls)); -  return (string)candidate_uri + loc[1..]; +  candidate_uri->path += loc[1..]; +  return (string)candidate_uri;    }    return 0;   }      /* By default, provide nothing. */   multiset(string) query_provides() { return 0; }         function(RequestID:int|mapping) query_seclevels()   {
Roxen.git/server/base_server/module.pike:477:    mapping(string:string) get_response_headers()    {    if (!response_headers) {    // Old kludge inherited from configuration.try_get_file.    if (!id->misc->common)    id->misc->common = ([]);       RequestID sub_id = id->clone_me();    sub_id->misc->common = id->misc->common;    -  sub_id->not_query = query_location() + path; -  sub_id->raw_url = replace (id->raw_url, id->not_query, sub_id->not_query); +  sub_id->raw_url = sub_id->not_query = query_location() + path; +  if ((sub_id->raw_url != id->raw_url) && (id->raw_url != id->not_query)) { +  // sub_id->raw_url = replace (id->raw_url, id->not_query, sub_id->not_query); +  sub_id->raw_url = sub_id->not_query + +  (({ "" }) + (id->raw_url/"?")[1..]) * "?"; +  }    sub_id->method = "HEAD";       mapping(string:mixed)|int(-1..0)|object res = find_file (path, sub_id);    if (res == -1) res = ([]);    else if (objectp (res)) {    string ext;    if(stringp(sub_id->extension)) {    sub_id->not_query += sub_id->extension;    ext = lower_case(Roxen.extension(sub_id->not_query, sub_id));    }
Roxen.git/server/base_server/module.pike:821:    foreach (prefix_locks; PATH; LOCKS) {CODE;} \    } while (0)      //! Find some or all locks that apply to @[path].   //!   //! @param path   //! Normalized path below the filesystem location.   //!   //! @param recursive   //! If @expr{1@} also return locks anywhere below @[path]. + //! If @expr{-1} return locks anywhere below @[path], but not + //! any above @[path]. (This is appropriate to use to get the + //! list of locks that need to be unlocked on DELETE.)   //!   //! @param exclude_shared   //! If @expr{1@} do not return shared locks that are held by users   //! other than the one the request is authenticated as. (This is   //! appropriate to get the list of locks that would conflict if the   //! current user were to make a shared lock.)   //!   //! @returns   //! Returns a multiset containing all applicable locks in   //! this location module, or @expr{0@} (zero) if there are none.   //!   //! @note   //! @[DAVLock] objects may be created if the filesystem has some   //! persistent storage of them. The default implementation does not   //! store locks persistently.   //!   //! @note   //! The default implementation only handles the @expr{"DAV:write"@}   //! lock type.   multiset(DAVLock) find_locks(string path, -  int(0..1) recursive, +  int(-1..1) recursive,    int(0..1) exclude_shared,    RequestID id)   {    // Common case.    if (!sizeof(file_locks) && !sizeof(prefix_locks)) return 0;       TRACE_ENTER(sprintf("find_locks(%O, %O, %O, X)",    path, recursive, exclude_shared), this);       string rsc = resource_id (path, id);
Roxen.git/server/base_server/module.pike:874:    }    else    add_locks = lambda (mapping(mixed:DAVLock) sub_locks) {    locks |= mkmultiset (values (sub_locks));    };       if (file_locks[rsc]) {    add_locks (file_locks[rsc]);    }    +  if (recursive >= 0) {    foreach(prefix_locks;    string prefix; mapping(mixed:DAVLock) sub_locks) {    if (has_prefix(rsc, prefix)) {    add_locks (sub_locks);    break;    }    } -  +  }       if (recursive) {    LOOP_OVER_BOTH (string prefix, mapping(mixed:DAVLock) sub_locks, {    if (has_prefix(prefix, rsc)) {    add_locks (sub_locks);    }    });    }       add_locks = 0;
Roxen.git/server/base_server/module.pike:1112:   {    TRACE_ENTER(sprintf("unregister_lock(%O, lock(%O), X).", path, lock->locktoken),    this);    mixed auth_user = id && authenticated_user_id (path, id);    path = resource_id (path, id);    DAVLock removed_lock;    if (lock->recursive) {    if (id) {    removed_lock = m_delete(prefix_locks[path], auth_user);    } else { -  foreach(prefix_locks[path]; mixed user; DAVLock l) { +  foreach(prefix_locks[path]||([]); mixed user; DAVLock l) {    if (l == lock) {    removed_lock = m_delete(prefix_locks[path], user);    }    }    }    if (!sizeof (prefix_locks[path])) m_delete (prefix_locks, path);    }    else if (file_locks[path]) {    if (id) {    removed_lock = m_delete (file_locks[path], auth_user);    } else { -  foreach(file_locks[path]; mixed user; DAVLock l) { +  foreach(file_locks[path]||([]); mixed user; DAVLock l) {    if (l == lock) {    removed_lock = m_delete(file_locks[path], user);    }    }    }    if (!sizeof (file_locks[path])) m_delete (file_locks, path);    } -  ASSERT_IF_DEBUG (lock /*%O*/ == removed_lock /*%O*/, lock, removed_lock); +  // NB: The lock may have already been removed in the !id case. +  ASSERT_IF_DEBUG (!(id || removed_lock) || +  (lock /*%O*/ == removed_lock /*%O*/), +  lock, removed_lock);    TRACE_LEAVE("Ok.");    return 0;   }      //! Register @[lock] on the path @[path] under the assumption that   //! there is no other lock already that conflicts with this one, i.e.   //! that @expr{check_locks(path,lock->recursive,id)@} would return   //! @expr{LOCK_NONE@} if @expr{lock->lockscope@} is   //! @expr{"DAV:exclusive"@}, or @expr{<= LOCK_SHARED_AT@} if   //! @expr{lock->lockscope@} is @expr{"DAV:shared"@}.
Roxen.git/server/base_server/module.pike:1342:    TRACE_LEAVE("Found match.");    SIMPLE_TRACE_LEAVE("Ok%s.",    got_sublocks ? " (this level only)" : "");    return got_sublocks; // Found matching sub-condition.    }    SIMPLE_TRACE_LEAVE("Conditional ok, but still locked (locktoken: %O).",    lock->locktoken);    locked_fail = 1;    }    -  TRACE_LEAVE("Failed."); +  if (locked_fail) { +  TRACE_LEAVE("Failed (locked)."); +  } else { +  TRACE_LEAVE("Precondition failed."); +  }    return Roxen.http_status(locked_fail ?    Protocols.HTTP.DAV_LOCKED :    Protocols.HTTP.HTTP_PRECOND_FAILED);   }      //! Used by some default implementations to check if we may perform a   //! write access to @[path]. It should at least call   //! @[check_if_header] to check DAV locks. It takes the same arguments   //! and has the same return value as that function.   //!   //! WARNING: This function has some design issues and will very likely   //! get a different interface. Compatibility is NOT guaranteed.   //!   //! A filesystem module should typically put all needed write access   //! checks here and then use this from @[find_file()],   //! @[delete_file()] etc. -  + //! + //! @returns + //! Returns @expr{0@} (zero) on success, a status mapping on + //! failure, or @expr{1@} if @[recursive] is set and write access is + //! allowed on this level but maybe not somewhere below. The caller + //! should in the last case do the operation on this level if + //! possible and then handle each member in the directory + //! recursively with @[write_access] etc.   protected mapping(string:mixed)|int(0..1) write_access(string relative_path,    int(0..1) recursive,    RequestID id)   {    return check_if_header (relative_path, recursive, id);   }    -  + //! + protected variant mapping(string:mixed)|int(0..1) write_access(array(string) paths, +  int(0..1) recursive, +  RequestID id) + { +  mapping(string:mixed)|int(0..1) ret; +  int got_ok; +  foreach(paths, string path) { +  ret = write_access(path, recursive, id); +  if (!ret) { +  got_ok = 1; +  continue; +  } +  if (ret == 1) { +  continue; +  } +  if (ret->error == Protocols.HTTP.HTTP_PRECOND_FAILED) { +  continue; +  } +  return ret; +  } +  +  if (got_ok) { +  // The if headers are valid for at least one of the paths, +  // and none of the other paths are locked. +  return 0; +  } +  +  // HTTP_PRECOND_FAILED for all of the paths. +  return ret; + } +    mapping(string:mixed)|int(-1..0)|Stdio.File find_file(string path,    RequestID id);      //! Used by the default @[recurse_delete_files] implementation to   //! delete a file or an empty directory.   //!   //! @returns   //! Returns a 2xx series status mapping on success (typically 204 No   //! Content). Returns 0 if the file doesn't exist. Returns an   //! appropriate status mapping for any other error.
Roxen.git/server/base_server/module.pike:2027:    Stdio.File file=Stdio.File();    if(!file->open(path,"r")) return 0;    if(has_suffix(index, "()"))    index = index[..sizeof(index) - 3];       // Pass path to original file so that include statements for local files    // work correctly.    return compile_string((pre || "") + file->read(), path)[index];   }    + #if constant(roxen.FSGarbWrapper) + //! Register a filesystem path for automatic garbage collection. + //! + //! @param path + //! Path in the real filesystem to garbage collect. + //! + //! @param max_age + //! Maximum allowed age in seconds for files. + //! + //! @param max_size + //! Maximum total size in bytes for all files under the path. + //! Zero to disable the limit. + //! + //! @param max_files + //! Maximum number of files under the path. + //! Zero to disable the limit. + //! + //! @returns + //! Returns a roxen.FSGarbWrapper object. The garbage collector + //! will be removed when this object is destructed (eg via + //! refcount-garb). + roxen.FSGarbWrapper register_fsgarb(string path, int max_age, +  int|void max_size, int|void max_files, +  string|void quarantine) + { +  return roxen.register_fsgarb(module_identifier(), path, max_age, +  max_size, max_files, quarantine); + } + #endif +    private mapping __my_tables = ([]);      array(mapping(string:mixed)) sql_query( string query, mixed ... args )   //! Do a SQL-query using @[get_my_sql], the table names in the query   //! should be written as &table; instead of table. As an example, if   //! the tables 'meta' and 'data' have been created with create_tables   //! or get_my_table, this query will work:   //!   //! SELECT &meta;.id AS id, &data;.data as DATA   //! FROM &data;, &meta; WHERE &my.meta;.xsize=200
Roxen.git/server/base_server/module.pike:2239:   //! Return a SQL-object for the database set with @[set_my_db],   //! defaulting to the 'shared' database. If @[read_only] is specified,   //! the database will be opened in read_only mode. @[charset] may be   //! used to specify a charset for the connection if the database   //! supports it.   //!   //! See also @[DBManager.get]   {    return DBManager.cached_get( my_db, _my_configuration, read_only, charset );   } +  + // Callback used by the DB browser, if defined, for custom formatting + // of database fields. + int|string format_db_browser_value (string db_name, string table_name, +  string column_name, array(string) col_names, +  array(string) col_types, array(string) row, +  RequestID id);