Roxen.git
/
server
/
base_server
/
module.pike
version
»
Context lines:
10
20
40
80
file
none
3
Roxen.git/server/base_server/module.pike:1:
-
// This file is part of Roxen WebServer.
-
// Copyright © 1996 - 2009, Roxen IS.
-
// $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;
-
-
inherit "basic_defvar";
-
mapping(string:array(int)) error_log=([]);
-
-
constant is_module = 1;
-
// constant module_type = MODULE_ZERO;
-
// constant module_name = "Unnamed module";
-
// constant module_doc = "Undocumented";
-
constant module_unique = 1;
-
-
//! Specifies that the module is opaque when it comes WebDAV
-
//! requests. Normally, recursive WebDAV requests will iterate through
-
//! all matching location modules even after a successful result has
-
//! been returned by some module. With this flag set, iteration will
-
//! stop after the call to this module. Useful if the module wants to
-
//! handle all requests for the specified location itself with no
-
//! fallback to other modules.
-
constant webdav_opaque = 0;
-
-
private Configuration _my_configuration;
-
private string _module_local_identifier;
-
private string _module_identifier =
-
lambda() {
-
mixed init_info = roxen->bootstrap_info->get();
-
if (arrayp (init_info)) {
-
[_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. :-)
-
*/
-
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
-
//! within the configuration. The returned string is the same as the
-
//! part after the first '/' in the one returned from
-
//! @[module_identifier].
-
{
-
return _module_local_identifier;
-
}
-
-
RoxenModule this_module()
-
{
-
return this_object(); // To be used from subclasses.
-
}
-
-
//! @ignore
-
DECLARE_OBJ_COUNT;
-
//! @endignore
-
-
string _sprintf()
-
{
-
return sprintf ("RoxenModule(%s)" + OBJ_COUNT, _module_identifier || "?");
-
}
-
-
array register_module()
-
{
-
return ({
-
this_object()->module_type,
-
this_object()->module_name,
-
this_object()->module_doc,
-
0,
-
module_unique,
-
this_object()->module_locked,
-
this_object()->module_counter,
-
});
-
}
-
-
string fix_cvs(string from)
-
{
-
from = replace(from, ({ "$", "Id: "," Exp $" }), ({"","",""}));
-
sscanf(from, "%*s,v %s", from);
-
return replace(from,"/","-");
-
}
-
-
int module_dependencies(Configuration configuration,
-
array (string) modules,
-
int|void now)
-
//! If your module depends on other modules, call this function to
-
//! ensure that those modules get loaded.
-
//!
-
//! @param configuration
-
//! The configuration for the modules. Use zero for the same as this
-
//! module.
-
//!
-
//! @param modules
-
//! An array of module identifiers. A module identifier is either
-
//! the name of the pike file minus extension, or a string on the
-
//! form that @[Roxen.get_modname] returns. In the latter case, the
-
//! @tt{<config name>@} and @tt{<copy>@} parts are ignored.
-
//!
-
//! @param now
-
//! If this flag is nonzero then any modules that aren't already
-
//! loaded get loaded (and started) right away, so that the code
-
//! following the @[module_dependencies] call can start using them.
-
//!
-
//! Otherwise the function only ensures that the modules exist in
-
//! the configuration and they will be loaded sometime during the
-
//! startup of it.
-
//!
-
//! @note
-
//! This function is only intended to be called from @[start].
-
//!
-
//! @note
-
//! The @[now] flag does not affect calls to
-
//! @[ready_to_receive_requests] in the listed modules. I.e. even if
-
//! you have declared a dependency on a module and have @[now] set,
-
//! you cannot assume its @[ready_to_receive_requests] function has
-
//! run (in fact, you can almost safely assume it hasn't). You can
-
//! however assume that its @[start] function has been called.
-
{
-
modules = map (modules,
-
lambda (string modname) {
-
sscanf ((modname / "/")[-1], "%[^#]", modname);
-
return modname;
-
});
-
Configuration conf = configuration || my_configuration();
-
if (!conf)
-
report_warning ("Configuration not resolved; module(s) %s that %O "
-
"depend on weren't added.\n", String.implode_nicely (modules),
-
module_identifier() ||
-
master()->describe_program(this_program) ||
-
"unknown module");
-
else
-
conf->add_modules( modules, now );
-
return 1;
-
}
-
-
string file_name_and_stuff()
-
{
-
return ("<b>Loaded from:</b> "+(roxen->filename(this_object()))+"<br>"+
-
(this_object()->cvs_version?
-
"<b>CVS Version:</b> "+
-
fix_cvs(this_object()->cvs_version)+"\n":""));
-
}
-
-
-
Configuration my_configuration()
-
//! Returns the Configuration object of the virtual server the module
-
//! 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;
-
}
-
-
void set_module_url(string to)
-
//! A common way of referring to a location where you maintain
-
//! information about your module or similar. The URL will turn up
-
//! on the module's information page in the admin interface,
-
//! referred to as the module's home page.
-
{
-
module_url = to;
-
}
-
-
void free_some_sockets_please(){}
-
-
// These functions have bodies to make module inheritance easier: An
-
// inheriting module can always assume that the module it inherits
-
// have these functions defined and properly proxy the calls to them.
-
// Thus an inherited module should be free to define them later on
-
// even if it didn't have them initially. (Modules need never proxy
-
// calls to the default implementations here.)
-
void start(int variable_save, Configuration conf, void|int newly_added) {}
-
void stop() {}
-
void ready_to_receive_requests (Configuration conf) {}
-
-
string status() {}
-
-
string info(Configuration conf)
-
{
-
return (this_object()->register_module()[2]);
-
}
-
-
string sname( )
-
{
-
return my_configuration()->otomod[ this_object() ];
-
}
-
-
ModuleInfo my_moduleinfo( )
-
//! Returns the associated @[ModuleInfo] object
-
{
-
string f = sname();
-
if( f ) return roxen.find_module( (f/"#")[0] );
-
}
-
-
void save_me()
-
{
-
my_configuration()->save_one( this_object() );
-
my_configuration()->module_changed( my_moduleinfo(), this_object() );
-
}
-
-
void save() { save_me(); }
-
string comment() { return ""; }
-
-
string query_internal_location()
-
//! Returns the internal mountpoint, where <ref>find_internal()</ref>
-
//! is mounted. It always ends with a '/'.
-
{
-
if(!_my_configuration)
-
error("Please do not call this function from create()!\n");
-
return _my_configuration->query_internal_location(this_object());
-
}
-
-
string query_absolute_internal_location(RequestID id)
-
//! Returns the internal mountpoint as an absolute path. It always
-
//! ends with a '/'.
-
{
-
return (id->misc->site_prefix_path || "") + query_internal_location();
-
}
-
-
string query_location()
-
//! Returns the mountpoint as an absolute path. The default
-
//! implementation uses the "location" configuration variable in the
-
//! module.
-
{
-
string s;
-
catch{s = query("location");};
-
return s;
-
}
-
-
array(string) location_urls()
-
//! Returns an array of all locations where the module is mounted.
-
{
-
string loc = query_location();
-
if (!loc) return ({});
-
if(!_my_configuration)
-
error("Please do not call this function from create()!\n");
-
array(string) urls = copy_value(_my_configuration->query("URLs"));
-
string hostname;
-
if (string world_url = _my_configuration->query ("MyWorldLocation"))
-
if (sizeof(world_url)) {
-
Standards.URI uri = Standards.URI(world_url);
-
hostname = uri->host;
-
}
-
if (!hostname) hostname = gethostname();
-
for (int i = 0; i < sizeof (urls); i++)
-
{
-
urls[i] = (urls[i]/"#")[0];
-
if (sizeof (urls[i]/"*") == 2)
-
urls[i] = replace(urls[i], "*", hostname);
-
}
-
return map (urls, `+, loc[1..]);
-
}
-
-
string location_url()
-
//! Returns an http or https url including the modules mountpoint. The
-
//! ip-number for the corresponding port will be added to the fragment
-
//! of the url. An http url will be prioritized over an https url.
-
{
-
string short_array(array a)
-
{
-
return "({ " + (map(a, lambda(object o) {
-
return sprintf("%O", o);
-
})*", ") + " })";
-
};
-
string loc = query_location();
-
if(!loc) return 0;
-
if(!_my_configuration)
-
error("Please do not call this function from create()!\n");
-
string hostname;
-
string world_url = _my_configuration->query("MyWorldLocation");
-
if (world_url && sizeof(world_url)) {
-
Standards.URI uri = Standards.URI(world_url);
-
hostname = uri->host;
-
}
-
if(!hostname)
-
hostname = gethostname();
-
#ifdef LOCATION_URL_DEBUG
-
werror(" Hostname: %O\n", hostname);
-
#endif
-
Standards.URI candidate_uri;
-
array(string) urls =
-
filter(_my_configuration->registered_urls, has_prefix, "http:") +
-
filter(_my_configuration->registered_urls, has_prefix, "https:");
-
foreach(urls, string url)
-
{
-
#ifdef LOCATION_URL_DEBUG
-
werror(" URL: %s\n", url);
-
#endif
-
mapping url_info = roxen.urls[url];
-
if(!url_info || !url_info->port || url_info->conf != _my_configuration)
-
continue;
-
Protocol p = url_info->port;
-
#ifdef LOCATION_URL_DEBUG
-
werror(" Protocol: %s\n", p);
-
#endif
-
Standards.URI uri = Standards.URI(url);
-
string ip = p->ip || "127.0.0.1";
-
if (ip == "::")
-
ip = "::1";
-
uri->fragment = "ip=" + ip;
-
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;
-
}
-
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));
-
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()
-
{
-
if(catch(query("_seclevels")) || (query("_seclevels") == 0))
-
return 0;
-
return roxen.compile_security_pattern(query("_seclevels"),this_object());
-
}
-
-
void set_status_for_path (string path, RequestID id, int status_code,
-
string|void message, mixed... args)
-
//! Register a status to be included in the response that applies only
-
//! for the given path. This is used for recursive operations that can
-
//! yield different results for different encountered files or
-
//! directories.
-
//!
-
//! The status is stored in the @[MultiStatus] object returned by
-
//! @[id->get_multi_status]. The server will use it to make a 207
-
//! Multi-Status response iff the module returns an empty mapping as
-
//! response.
-
//!
-
//! @param path
-
//! Path (below the filesystem location) to which the status applies.
-
//!
-
//! @param status_code
-
//! The HTTP status code.
-
//!
-
//! @param message
-
//! If given, it's a message to include in the response. The
-
//! message may contain line feeds ('\n') and ISO-8859-1
-
//! characters in the ranges 32..126 and 128..255. Line feeds are
-
//! converted to spaces if the response format doesn't allow them.
-
//!
-
//! @param args
-
//! If there are more arguments after @[message] then @[message]
-
//! is taken as an @[sprintf] style format string which is used to
-
//! format @[args].
-
//!
-
//! @note
-
//! This function is just a wrapper for @[id->set_status_for_path]
-
//! that corrects for the filesystem location.
-
//!
-
//! @seealso
-
//! @[RequestID.set_status_for_path], @[Roxen.http_status]
-
{
-
if (sizeof (args)) message = sprintf (message, @args);
-
id->set_status_for_path (query_location() + path, status_code, message);
-
}
-
-
Stat stat_file(string f, RequestID id){}
-
array(string) find_dir(string f, RequestID id){}
-
mapping(string:Stat) find_dir_stat(string f, RequestID id)
-
{
-
SIMPLE_TRACE_ENTER(this, "find_dir_stat(): %O", f);
-
-
array(string) files = find_dir(f, id);
-
mapping(string:Stat) res = ([]);
-
-
foreach(files || ({}), string fname) {
-
SIMPLE_TRACE_ENTER(this, "stat()'ing %O", f + "/" + fname);
-
Stat st = stat_file(replace(f + "/" + fname, "//", "/"), id);
-
if (st) {
-
res[fname] = st;
-
TRACE_LEAVE("OK");
-
} else {
-
TRACE_LEAVE("No stat info");
-
}
-
}
-
-
TRACE_LEAVE("");
-
return(res);
-
}
-
-
class DefaultPropertySet
-
{
-
inherit PropertySet;
-
-
protected Stat stat;
-
-
protected void create (string path, string abs_path, RequestID id, Stat stat)
-
{
-
::create (path, abs_path, id);
-
this_program::stat = stat;
-
}
-
-
Stat get_stat() {return stat;}
-
-
protected mapping(string:string) response_headers;
-
-
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->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));
-
}
-
array(string) tmp=sub_id->conf->type_from_filename(sub_id->not_query, 1, ext);
-
if(tmp)
-
res = ([ "file":res, "type":tmp[0], "encoding":tmp[1] ]);
-
else
-
res = (["file": res]);
-
}
-
response_headers = sub_id->make_response_headers (res);
-
destruct (sub_id);
-
}
-
-
return response_headers;
-
}
-
}
-
-
//! Return the set of properties for @[path].
-
//!
-
//! @returns
-
//! Returns @tt{0@} (zero) if @[path] does not exist.
-
//!
-
//! Returns an error mapping if there's some other error accessing
-
//! the properties.
-
//!
-
//! Otherwise returns a @[PropertySet] object.
-
//!
-
//! @seealso
-
//! @[query_property()]
-
PropertySet|mapping(string:mixed) query_property_set(string path, RequestID id)
-
{
-
SIMPLE_TRACE_ENTER (this, "Querying properties on %O", path);
-
Stat st = stat_file(path, id);
-
-
if (!st) {
-
SIMPLE_TRACE_LEAVE ("No such file or dir");
-
return 0;
-
}
-
-
PropertySet res = DefaultPropertySet(path, query_location()+path, id, st);
-
SIMPLE_TRACE_LEAVE ("");
-
return res;
-
}
-
-
//! Returns the value of the specified property, or an error code
-
//! mapping.
-
//!
-
//! @note
-
//! Returning a string is shorthand for returning an array
-
//! with a single text node.
-
//!
-
//! @seealso
-
//! @[query_property_set()]
-
string|array(Parser.XML.Tree.SimpleNode)|mapping(string:mixed)
-
query_property(string path, string prop_name, RequestID id)
-
{
-
mapping(string:mixed)|PropertySet properties = query_property_set(path, id);
-
if (!properties) {
-
return Roxen.http_status(Protocols.HTTP.HTTP_NOT_FOUND,
-
"No such file or directory.");
-
}
-
if (mappingp (properties))
-
return properties;
-
return properties->query_property(prop_name) ||
-
Roxen.http_status(Protocols.HTTP.HTTP_NOT_FOUND, "No such property.");
-
}
-
-
//! RFC 2518 PROPFIND implementation with recursion according to
-
//! @[depth]. See @[PropertySet()->find_properties()] for details.
-
//!
-
//! @seealso
-
//! @[query_property_set()]
-
mapping(string:mixed) recurse_find_properties(string path, string mode,
-
int depth, RequestID id,
-
multiset(string)|void filt)
-
{
-
string prefix = map(query_location()[1..]/"/", Roxen.http_encode_url)*"/";
-
MultiStatus.Prefixed result =
-
id->get_multi_status()->prefix (id->url_base() + prefix);
-
-
mapping(string:mixed) recurse (string path, int depth) {
-
SIMPLE_TRACE_ENTER (this, "%s for %O, depth %d",
-
mode == "DAV:propname" ? "Listing property names" :
-
mode == "DAV:allprop" ? "Retrieving all properties" :
-
mode == "DAV:prop" ? "Retrieving specific properties" :
-
"Finding properties with mode " + mode,
-
path, depth);
-
mapping(string:mixed)|PropertySet properties = query_property_set(path, id);
-
-
if (!properties) {
-
SIMPLE_TRACE_LEAVE ("No such file or dir");
-
return 0;
-
}
-
-
{
-
mapping(string:mixed) ret = mappingp (properties) ?
-
properties : properties->find_properties(mode, result, filt);
-
-
if (ret) {
-
SIMPLE_TRACE_LEAVE ("Got status %d: %O", ret->error, ret->rettext);
-
return ret;
-
}
-
}
-
-
if (properties->get_stat()->isdir) {
-
if (depth <= 0) {
-
SIMPLE_TRACE_LEAVE ("Not recursing due to depth limit");
-
return ([]);
-
}
-
depth--;
-
foreach(find_dir(path, id) || ({}), string filename) {
-
filename = combine_path_unix(path, filename);
-
if (mapping(string:mixed) sub_res = recurse(filename, depth))
-
if (sizeof (sub_res))
-
result->add_status (filename, sub_res->error, sub_res->rettext);
-
}
-
}
-
-
SIMPLE_TRACE_LEAVE ("");
-
return ([]);
-
};
-
-
return recurse (path, depth);
-
}
-
-
mapping(string:mixed) patch_properties(string path,
-
array(PatchPropertyCommand) instructions,
-
RequestID id)
-
{
-
SIMPLE_TRACE_ENTER (this, "Patching properties for %O", path);
-
mapping(string:mixed)|PropertySet properties = query_property_set(path, id);
-
-
if (!properties) {
-
SIMPLE_TRACE_LEAVE ("No such file or dir");
-
return 0;
-
}
-
if (mappingp (properties)) {
-
SIMPLE_TRACE_LEAVE ("Got error %d from query_property_set: %O",
-
properties->error, properties->rettext);
-
return properties;
-
}
-
-
mapping(string:mixed) errcode;
-
-
if (errcode = write_access(path, 0, id)) {
-
SIMPLE_TRACE_LEAVE("Patching denied by write_access().");
-
return errcode;
-
}
-
-
if (errcode = properties->start()) {
-
SIMPLE_TRACE_LEAVE ("Got error %d from PropertySet.start: %O",
-
errcode->error, errcode->rettext);
-
return errcode;
-
}
-
-
array(mapping(string:mixed)) results;
-
-
mixed err = catch {
-
results = instructions->execute(properties);
-
};
-
if (err) {
-
properties->unroll();
-
throw (err);
-
} else {
-
string prefix = map((query_location()[1..] + path)/"/",
-
Roxen.http_encode_url)*"/";
-
MultiStatus.Prefixed result =
-
id->get_multi_status()->prefix (id->url_base() + prefix);
-
int any_failed;
-
foreach(results, mapping(string:mixed) answer) {
-
if (any_failed = (answer && (answer->error >= 300))) {
-
break;
-
}
-
}
-
if (any_failed) {
-
// Unroll and fail any succeeded items.
-
int i;
-
mapping(string:mixed) answer =
-
Roxen.http_status (Protocols.HTTP.DAV_FAILED_DEP);
-
for(i = 0; i < sizeof(results); i++) {
-
if (!results[i] || results[i]->error < 300) {
-
result->add_property("", instructions[i]->property_name,
-
answer);
-
} else {
-
result->add_property("", instructions[i]->property_name,
-
results[i]);
-
}
-
}
-
properties->unroll();
-
} else {
-
int i;
-
for(i = 0; i < sizeof(results); i++) {
-
result->add_property("", instructions[i]->property_name,
-
results[i]);
-
}
-
properties->commit();
-
}
-
}
-
-
SIMPLE_TRACE_LEAVE ("");
-
return 0;
-
}
-
-
//! Convenience variant of @[patch_properties()] that sets a single
-
//! property.
-
//!
-
//! @returns
-
//! Returns a mapping on any error, zero otherwise.
-
mapping(string:mixed) set_property (string path, string prop_name,
-
string|array(Parser.XML.Tree.SimpleNode) value,
-
RequestID id)
-
{
-
mapping(string:mixed)|PropertySet properties = query_property_set(path, id);
-
if (!properties) return Roxen.http_status(Protocols.HTTP.HTTP_NOT_FOUND,
-
"File not found.");
-
if (mappingp (properties)) return properties;
-
-
mapping(string:mixed) result = properties->start();
-
if (result) return result;
-
-
result = properties->set_property(prop_name, value);
-
if (result && result->error >= 300) {
-
properties->unroll();
-
return result;
-
}
-
-
properties->commit();
-
return 0;
-
}
-
-
//! Convenience variant of @[patch_properties()] that removes a single
-
//! property.
-
//!
-
//! @returns
-
//! Returns a mapping on any error, zero otherwise.
-
mapping(string:mixed) remove_property (string path, string prop_name,
-
RequestID id)
-
{
-
mapping(string:mixed)|PropertySet properties = query_property_set(path, id);
-
if (!properties) return Roxen.http_status(Protocols.HTTP.HTTP_NOT_FOUND,
-
"File not found.");
-
if (mappingp (properties)) return properties;
-
-
mapping(string:mixed) result = properties->start();
-
if (result) return result;
-
-
result = properties->remove_property(prop_name);
-
if (result && result->error >= 300) {
-
properties->unroll();
-
return result;
-
}
-
-
properties->commit();
-
return 0;
-
}
-
-
string resource_id (string path, RequestID|int(0..0) id)
-
//! Return a string that within the filesystem uniquely identifies the
-
//! resource on @[path] in the given request. This is commonly @[path]
-
//! itself but can be extended with e.g. language, user or some form
-
//! variable if the path is mapped to different files according to
-
//! those fields.
-
//!
-
//! The important criteria here is that every unique returned string
-
//! corresponds to a resource that can be changed independently of
-
//! every other. Thus e.g. dynamic pages that evaluate to different
-
//! results depending on variables or cookies etc should _not_ be
-
//! mapped to more than one string by this function. It also means
-
//! that if files are stored in a filesystem which is case insensitive
-
//! then this function should normalize case differences.
-
//!
-
//! This function is used e.g by the default lock implementation to
-
//! convert paths to resources that can be locked independently of
-
//! each other. There's also a notion of recursive locks there, which
-
//! means that a recursive lock on a certain resource identifier also
-
//! locks every resource whose identifier it is a prefix of. Therefore
-
//! it's typically necessary to ensure that every identifier ends with
-
//! "/" so that a recursive lock on e.g. "doc/foo" doesn't lock
-
//! "doc/foobar".
-
//!
-
//! @param path
-
//! The requested path below the filesystem location. It has been
-
//! normalized with @[VFS.normalize_path].
-
//!
-
//! @param id
-
//! The request id may have the value @expr{0@} (zero) if called
-
//! by @[Configuration()->expire_locks()].
-
{
-
return has_suffix (path, "/") ? path : path + "/";
-
}
-
-
string|int authenticated_user_id (string path, RequestID id)
-
//! Return a value that uniquely identifies the user that the given
-
//! request is authenticated as.
-
//!
-
//! This function is e.g. used by the default lock implementation to
-
//! tell different users holding locks apart. WARNING: Due to some
-
//! design issues in the lock system, it's likely that it will change
-
//! to not use this function in the future.
-
//!
-
//! @param path
-
//! The requested path below the filesystem location. It has been
-
//! normalized with @[VFS.normalize_path].
-
{
-
// Leave this to the standard auth system by default.
-
User uid = my_configuration()->authenticate (id);
-
return uid && uid->name();
-
}
-
-
// Mapping from resource id to a mapping from user id to the lock
-
// that apply to the resource.
-
//
-
// Only used internally by the default lock implementation.
-
protected mapping(string:mapping(mixed:DAVLock)) file_locks = ([]);
-
-
// Mapping from resource id to a mapping from user id to the lock
-
// that apply recursively to the resource and all other resources
-
// it's a prefix of.
-
//
-
// Only used internally by the default lock implementation.
-
protected mapping(string:mapping(mixed:DAVLock)) prefix_locks = ([]);
-
-
#define LOOP_OVER_BOTH(PATH, LOCKS, CODE) \
-
do { \
-
foreach (file_locks; PATH; LOCKS) {CODE;} \
-
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].
-
//!
-
//! @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(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);
-
-
multiset(DAVLock) locks = (<>);
-
function(mapping(mixed:DAVLock):void) add_locks;
-
-
if (exclude_shared) {
-
mixed auth_user = authenticated_user_id (path, id);
-
add_locks = lambda (mapping(mixed:DAVLock) sub_locks) {
-
foreach (sub_locks; string user; DAVLock lock)
-
if (user == auth_user ||
-
lock->lockscope == "DAV:exclusive")
-
locks[lock] = 1;
-
};
-
}
-
else
-
add_locks = lambda (mapping(mixed:DAVLock) sub_locks) {
-
locks |= mkmultiset (values (sub_locks));
-
};
-
-
if (file_locks[rsc]) {
-
add_locks (file_locks[rsc]);
-
}
-
-
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;
-
-
TRACE_LEAVE(sprintf("Done, found %d locks.", sizeof(locks)));
-
-
return sizeof(locks) && locks;
-
}
-
-
//! Check if there are one or more locks that apply to @[path] for the
-
//! user the request is authenticated as.
-
//!
-
//! WARNING: This function has some design issues and will very likely
-
//! get a different interface. Compatibility is NOT guaranteed.
-
//!
-
//! @param path
-
//! Normalized path below the filesystem location.
-
//!
-
//! @param recursive
-
//! If @expr{1@} also check recursively under @[path] for locks.
-
//!
-
//! @returns
-
//! The valid return values are:
-
//! @mixed
-
//! @type DAVLock
-
//! The lock owned by the authenticated user that apply to
-
//! @[path]. (It doesn't matter if the @expr{recursive@} flag in
-
//! the lock doesn't match the @[recursive] argument.)
-
//! @type LockFlag
-
//! @int
-
//! @value LOCK_NONE
-
//! No locks apply. (0)
-
//! @value LOCK_SHARED_BELOW
-
//! There are only one or more shared locks held by other
-
//! users somewhere below @[path] (but not on @[path]
-
//! itself). Only returned if @[recursive] is set. (2)
-
//! @value LOCK_SHARED_AT
-
//! There are only one or more shared locks held by other
-
//! users on @[path]. (3)
-
//! @value LOCK_OWN_BELOW
-
//! The authenticated user has locks under @[path] (but not
-
//! on @[path] itself) and there are no exclusive locks held
-
//! by other users. Only returned if @[recursive] is set. (4)
-
//! @value LOCK_EXCL_BELOW
-
//! There are one or more exclusive locks held by other
-
//! users somewhere below @[path] (but not on @[path]
-
//! itself). Only returned if @[recursive] is set. (6)
-
//! @value LOCK_EXCL_AT
-
//! There are one or more exclusive locks held by other
-
//! users on @[path]. (7)
-
//! @endint
-
//! Note that the lowest bit is set for all flags that apply to
-
//! @[path] itself.
-
//! @endmixed
-
//!
-
//! @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.
-
DAVLock|LockFlag check_locks(string path,
-
int(0..1) recursive,
-
RequestID id)
-
{
-
TRACE_ENTER(sprintf("check_locks(%O, %d, X)", path, recursive), this);
-
-
// Common case.
-
if (!sizeof(file_locks) && !sizeof(prefix_locks)) {
-
TRACE_LEAVE ("Got no locks");
-
return 0;
-
}
-
-
mixed auth_user = authenticated_user_id (path, id);
-
path = resource_id (path, id);
-
-
if (DAVLock lock =
-
file_locks[path] && file_locks[path][auth_user] ||
-
prefix_locks[path] && prefix_locks[path][auth_user]) {
-
TRACE_LEAVE(sprintf("Found own lock %O.", lock->locktoken));
-
return lock;
-
}
-
-
LockFlag shared;
-
-
if (mapping(mixed:DAVLock) locks = file_locks[path]) {
-
foreach(locks;; DAVLock lock) {
-
if (lock->lockscope == "DAV:exclusive") {
-
TRACE_LEAVE(sprintf("Found other user's exclusive lock %O.",
-
lock->locktoken));
-
return LOCK_EXCL_AT;
-
}
-
shared = LOCK_SHARED_AT;
-
break;
-
}
-
}
-
-
foreach(prefix_locks;
-
string prefix; mapping(mixed:DAVLock) locks) {
-
if (has_prefix(path, prefix)) {
-
if (DAVLock lock = locks[auth_user]) {
-
SIMPLE_TRACE_LEAVE ("Found own lock %O on %O.", lock->locktoken, prefix);
-
return lock;
-
}
-
if (!shared)
-
// If we've found a shared lock then we won't find an
-
// exclusive one anywhere else.
-
foreach(locks;; DAVLock lock) {
-
if (lock->lockscope == "DAV:exclusive") {
-
TRACE_LEAVE(sprintf("Found other user's exclusive lock %O.",
-
lock->locktoken));
-
return LOCK_EXCL_AT;
-
}
-
shared = LOCK_SHARED_AT;
-
break;
-
}
-
}
-
}
-
-
if (!recursive) {
-
SIMPLE_TRACE_LEAVE("Returning %O.", shared);
-
return shared;
-
}
-
-
int(0..1) locked_by_auth_user;
-
-
// We want to know if there are any locks with @[path] as prefix
-
// that apply to us.
-
LOOP_OVER_BOTH (string prefix, mapping(mixed:DAVLock) locks, {
-
if (has_prefix(prefix, path)) {
-
if (locks[auth_user])
-
locked_by_auth_user = 1;
-
else
-
foreach(locks;; DAVLock lock) {
-
if (lock->lockscope == "DAV:exclusive") {
-
TRACE_LEAVE(sprintf("Found other user's exclusive lock %O.",
-
lock->locktoken));
-
return LOCK_EXCL_BELOW;
-
}
-
if (!shared) shared = LOCK_SHARED_BELOW;
-
break;
-
}
-
}
-
});
-
-
SIMPLE_TRACE_LEAVE("Returning %O.", locked_by_auth_user ? LOCK_OWN_BELOW : shared);
-
return locked_by_auth_user ? LOCK_OWN_BELOW : shared;
-
}
-
-
//! 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_OWN_BELOW@} if
-
//! @expr{lock->lockscope@} is @expr{"DAV:shared"@}.
-
//!
-
//! This function is only provided as a helper to call from
-
//! @[lock_file] if the default lock implementation is to be used.
-
//!
-
//! @param path
-
//! Normalized path (below the filesystem location) that the lock
-
//! applies to.
-
//!
-
//! @param lock
-
//! The lock to register.
-
//!
-
//! @note
-
//! The default implementation only handles the @expr{"DAV:write"@}
-
//! lock type. It uses @[resource_id] to map paths to unique resources
-
//! and @[authenticated_user_id] to tell users apart.
-
protected void register_lock(string path, DAVLock lock, RequestID id)
-
{
-
TRACE_ENTER(sprintf("register_lock(%O, lock(%O), X).", path, lock->locktoken),
-
this);
-
ASSERT_IF_DEBUG (lock->locktype == "DAV:write");
-
mixed auth_user = authenticated_user_id (path, id);
-
path = resource_id (path, id);
-
if (lock->recursive) {
-
if (prefix_locks[path]) {
-
prefix_locks[path][auth_user] = lock;
-
} else {
-
prefix_locks[path] = ([ auth_user:lock ]);
-
}
-
} else {
-
if (file_locks[path]) {
-
file_locks[path][auth_user] = lock;
-
} else {
-
file_locks[path] = ([ auth_user:lock ]);
-
}
-
}
-
TRACE_LEAVE("Ok.");
-
}
-
-
//! Unregister @[lock] that currently is locking the resource at
-
//! @[path]. It's assumed that the lock is registered for exactly that
-
//! path.
-
//!
-
//! This function is only provided as a helper to call from
-
//! @[unlock_file] if the default lock implementation is to be used.
-
//!
-
//! @param path
-
//! Normalized path (below the filesystem location) that the lock
-
//! applies to.
-
//!
-
//! @param lock
-
//! The lock to unregister. (It must not be changed or destructed.)
-
//!
-
//! @param id
-
//! The request id may have the value @expr{0@} (zero) if called
-
//! by @[Configuration()->expire_locks()].
-
protected void unregister_lock (string path, DAVLock lock,
-
RequestID|int(0..0) id)
-
{
-
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) {
-
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) {
-
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);
-
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"@}.
-
//!
-
//! The implementation must at least support the @expr{"DAV:write"@}
-
//! lock type (RFC 2518, section 7). Briefly: An exclusive lock on a
-
//! file prohibits other users from changing its content. An exclusive
-
//! lock on a directory (aka collection) prohibits other users from
-
//! adding or removing files or directories in it. An exclusive lock
-
//! on a file or directory prohibits other users from setting or
-
//! deleting any of its properties. A shared lock prohibits users
-
//! without locks to do any of this, and it prohibits other users from
-
//! obtaining an exclusive lock. A resource that doesn't exist can be
-
//! locked, provided the directory it would be in exists (relaxed in
-
//! RFC 2518Bis (working draft)). The default implementation fulfills
-
//! these criteria.
-
//!
-
//! It's up to @[find_file] et al to actually check that the necessary
-
//! locks are held. It can preferably use @[write_access] for that,
-
//! which has a default implementation for checking
-
//! @expr{"DAV:write"@} locks.
-
//!
-
//! @param path
-
//! Normalized path (below the filesystem location) that the lock
-
//! applies to.
-
//!
-
//! @param lock
-
//! The lock to register.
-
//!
-
//! @returns
-
//! Returns @expr{0@} if the lock is successfully installed or if
-
//! locking isn't used. Returns a status mapping if an error
-
//! occurred.
-
//!
-
//! @note
-
//! To use the default lock implementation, call @[register_lock]
-
//! from this function.
-
mapping(string:mixed) lock_file(string path,
-
DAVLock lock,
-
RequestID id)
-
{
-
return 0;
-
}
-
-
//! Remove @[lock] that currently is locking the resource at @[path].
-
//! It's assumed that the lock is registered for exactly that path.
-
//!
-
//! @param path
-
//! Normalized path (below the filesystem location) that the lock
-
//! applies to.
-
//!
-
//! @param lock
-
//! The lock to unregister. (It must not be changed or destructed.)
-
//!
-
//! @param id
-
//! @mixed
-
//! @type RequestID
-
//! The request that attempted to unlock the lock. The function
-
//! should do the normal access checks before unlocking the lock
-
//! in this case.
-
//!
-
//! @type int(0..0)
-
//! The lock is unlocked internally by the server (typically due
-
//! to a timeout) and should be carried out without any access
-
//! checks. The function must succeed and return zero in this
-
//! case.
-
//! @endmixed
-
//!
-
//! @returns
-
//! Returns a status mapping on any error, zero otherwise.
-
//!
-
//! @note
-
//! To use the default lock implementation, call @[unregister_lock]
-
//! from this function.
-
mapping(string:mixed) unlock_file (string path,
-
DAVLock lock,
-
RequestID|int(0..0) id);
-
-
//! Checks that the conditions specified by the WebDAV @expr{"If"@}
-
//! header are fulfilled on the given path (RFC 2518 9.4). This means
-
//! that locks are checked as necessary using @[check_locks].
-
//!
-
//! WARNING: This function has some design issues and will very likely
-
//! get a different interface. Compatibility is NOT guaranteed.
-
//!
-
//! @param path
-
//! Path (below the filesystem location) that the lock applies to.
-
//!
-
//! @param recursive
-
//! If @expr{1@} also check write access recursively under @[path].
-
//!
-
//! @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.
-
mapping(string:mixed)|int(0..1) check_if_header(string relative_path,
-
int(0..1) recursive,
-
RequestID id)
-
{
-
SIMPLE_TRACE_ENTER(this, "Checking \"If\" header for %O",
-
relative_path);
-
-
int/*LockFlag*/|DAVLock lock = check_locks(relative_path, recursive, id);
-
-
int(0..1) got_sublocks;
-
if (lock && intp(lock)) {
-
if (lock & 1) {
-
TRACE_LEAVE("Locked by other user.");
-
return Roxen.http_status(Protocols.HTTP.DAV_LOCKED);
-
}
-
else if (recursive)
-
// This is set for LOCK_OWN_BELOW too since it might be
-
// necessary to come back here and check the If header for
-
// those locks.
-
got_sublocks = 1;
-
}
-
-
string path = relative_path;
-
if (!has_suffix (path, "/")) path += "/"; // get_if_data always adds a "/".
-
path = query_location() + path; // No need for fancy combine_path stuff here.
-
-
mapping(string:array(array(array(string)))) if_data = id->get_if_data();
-
array(array(array(string))) condition;
-
if (!if_data || !sizeof(condition = if_data[path] || if_data[0])) {
-
if (lock) {
-
TRACE_LEAVE("Locked, no if header.");
-
return Roxen.http_status(Protocols.HTTP.DAV_LOCKED);
-
}
-
SIMPLE_TRACE_LEAVE("No lock and no if header - ok%s.",
-
got_sublocks ? " (this level only)" : "");
-
return got_sublocks; // No condition and no lock -- Ok.
-
}
-
-
string|int(-1..0) etag;
-
-
int(0..1) locked_fail = !!lock;
-
next_condition:
-
foreach(condition, array(array(string)) sub_cond) {
-
SIMPLE_TRACE_ENTER(this,
-
"Trying condition ( %{%s:%O %})...", sub_cond);
-
int negate;
-
DAVLock locked = lock;
-
foreach(sub_cond, array(string) token) {
-
switch(token[0]) {
-
case "not":
-
negate = !negate;
-
break;
-
case "etag":
-
if (!etag) {
-
// Get the etag for this resource (if any).
-
// FIXME: We only support straight strings as etag properties.
-
if (!stringp(etag = query_property(relative_path,
-
"DAV:getetag", id))) {
-
etag = -1;
-
}
-
}
-
if (etag != token[1]) {
-
// No etag available for this resource, or mismatch.
-
if (!negate) {
-
TRACE_LEAVE("Etag mismatch.");
-
continue next_condition;
-
}
-
} else if (negate) {
-
// Etag match with negated expression.
-
TRACE_LEAVE("Matched negated etag.");
-
continue next_condition;
-
}
-
negate = 0;
-
break;
-
case "key":
-
// The user has specified a key, so don't fail with DAV_LOCKED.
-
locked_fail = 0;
-
if (negate) {
-
if (lock && lock->locktoken == token[1]) {
-
TRACE_LEAVE("Matched negated lock.");
-
continue next_condition; // Fail.
-
}
-
} else if (!lock || lock->locktoken != token[1]) {
-
// Lock mismatch.
-
TRACE_LEAVE("Lock mismatch.");
-
continue next_condition; // Fail.
-
} else {
-
locked = 0;
-
}
-
negate = 0;
-
break;
-
}
-
}
-
if (!locked) {
-
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.");
-
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.
-
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);
-
}
-
-
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.
-
//!
-
//! @note
-
//! The default implementation falls back to @[find_file()].
-
protected mapping(string:mixed) delete_file(string path, RequestID id)
-
{
-
// Fall back to find_file().
-
RequestID tmp_id = id->clone_me();
-
tmp_id->not_query = query_location() + path;
-
tmp_id->method = "DELETE";
-
// FIXME: Logging?
-
return find_file(path, tmp_id) ||
-
tmp_id->misc->error_code && Roxen.http_status (tmp_id->misc->error_code);
-
}
-
-
//! Delete @[path] recursively.
-
//!
-
//! The default implementation handles the recursion and calls
-
//! @[delete_file] for each file and 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. That includes an
-
//! empty mapping in case some subparts couldn't be deleted, to
-
//! signify a 207 Multi-Status response using the info in
-
//! @[id->get_multi_status()].
-
mapping(string:mixed) recurse_delete_files(string path,
-
RequestID id,
-
void|MultiStatus.Prefixed stat)
-
{
-
SIMPLE_TRACE_ENTER (this, "Deleting %O recursively", path);
-
if (!stat)
-
stat = id->get_multi_status()->prefix (id->url_base() +
-
query_location()[1..]);
-
-
Stat st = stat_file(path, id);
-
if (!st) {
-
SIMPLE_TRACE_LEAVE ("No such file or directory");
-
return 0;
-
}
-
-
mapping(string:mixed) recurse (string path, Stat st)
-
{
-
// Note: Already got an extra TRACE_ENTER level on entry here.
-
-
if (st->isdir) {
-
// RFC 2518 8.6.2
-
// The DELETE operation on a collection MUST act as if a
-
// "Depth: infinity" header was used on it.
-
int fail;
-
if (!has_suffix(path, "/")) path += "/";
-
foreach(find_dir(path, id) || ({}), string fname) {
-
fname = path + fname;
-
if (Stat sub_stat = stat_file (fname, id)) {
-
SIMPLE_TRACE_ENTER (this, "Deleting %O", fname);
-
if (mapping(string:mixed) sub_res = recurse(fname, sub_stat)) {
-
// RFC 2518 8.6.2
-
// Additionally 204 (No Content) errors SHOULD NOT be returned
-
// in the 207 (Multi-Status). The reason for this prohibition
-
// is that 204 (No Content) is the default success code.
-
if (sizeof (sub_res) && sub_res->error != 204) {
-
stat->add_status(fname, sub_res->error, sub_res->rettext);
-
}
-
if (!sizeof (sub_res) || sub_res->error >= 300) fail = 1;
-
}
-
}
-
}
-
if (fail) {
-
SIMPLE_TRACE_LEAVE ("Partial failure");
-
return ([]);
-
}
-
}
-
-
SIMPLE_TRACE_LEAVE ("");
-
return delete_file (path, id);
-
};
-
-
return recurse(path, st) || Roxen.http_status(204);
-
}
-
-
//! Make a new collection (aka directory) at @[path].
-
//!
-
//! @returns
-
//! Returns a 2xx series status on success (typically 201 Created).
-
//! Returns @expr{0@} (zero) if there's no directory to create the
-
//! new one in. Returns other result mappings on failure.
-
mapping(string:mixed) make_collection(string path, RequestID id)
-
{
-
// Fall back to find_file().
-
RequestID tmp_id = id->clone_me();
-
tmp_id->not_query = query_location() + path;
-
tmp_id->method = "MKCOL";
-
// FIXME: Logging?
-
return find_file(path, tmp_id);
-
}
-
-
//! Used by the default @[copy_collection] implementation to copy all
-
//! properties at @[source] to @[destination].
-
//!
-
//! @param source
-
//! Source path below the filesystem location.
-
//!
-
//! @param destination
-
//! Destination path below the filesystem location.
-
//!
-
//! @param behavior
-
//! Specifies how to copy properties. See the @[PropertyBehavior]
-
//! type for details.
-
//!
-
//! @returns
-
//! @expr{0@} (zero) on success or an appropriate status mapping for
-
//! any error.
-
protected mapping(string:mixed) copy_properties(
-
string source, string destination, PropertyBehavior behavior, RequestID id)
-
{
-
SIMPLE_TRACE_ENTER(this, "copy_properties(%O, %O, %O, %O)",
-
source, destination, behavior, id);
-
PropertySet source_properties = query_property_set(source, id);
-
PropertySet destination_properties = query_property_set(destination, id);
-
-
multiset(string) property_set = source_properties->query_all_properties();
-
mapping(string:mixed) res;
-
foreach(property_set; string property_name;) {
-
SIMPLE_TRACE_ENTER(this, "Copying the property %O.", property_name);
-
string|array(Parser.XML.Tree.SimpleNode)|mapping(string:mixed) source_val =
-
source_properties->query_property(property_name);
-
if (mappingp(source_val)) {
-
TRACE_LEAVE("Reading of property failed. Skipped.");
-
continue;
-
}
-
string|array(Parser.XML.Tree.SimpleNode)|mapping(string:mixed) dest_val =
-
destination_properties->query_property(property_name);
-
if (dest_val == source_val) {
-
TRACE_LEAVE("Destination already has the correct value.");
-
continue;
-
}
-
mapping(string:mixed) subres =
-
destination_properties->set_property(property_name, source_val);
-
if (!behavior) {
-
TRACE_LEAVE("Omit verify.");
-
continue;
-
}
-
if ((intp(behavior) || behavior[property_name]) && (subres->error < 300)) {
-
// FIXME: Check that if the property was live in source,
-
// it is still live in destination.
-
// This is likely already so, since we're in the same module.
-
}
-
if ((subres->error < 300) ||
-
(subres->error == Protocols.HTTP.HTTP_CONFLICT)) {
-
// Ok, or read-only property.
-
TRACE_LEAVE("Copy ok or read-only property.");
-
continue;
-
}
-
if (!subres) {
-
// Copy failed, but attempt to copy the rest.
-
res = Roxen.http_status(Protocols.HTTP.HTTP_PRECOND_FAILED);
-
}
-
TRACE_LEAVE("Copy failed.");
-
}
-
TRACE_LEAVE(res?"Failed.":"Ok.");
-
return res;
-
}
-
-
//! Used by the default @[recurse_copy_files] and @[move_collection]
-
//! to copy a collection (aka directory) and its properties but not
-
//! its contents.
-
//!
-
//! @param source
-
//! Source path below the filesystem location.
-
//!
-
//! @param destination
-
//! Destination path below the filesystem location.
-
//!
-
//! @param behavior
-
//! Specifies how to copy properties. See the @[PropertyBehavior]
-
//! type for details.
-
//!
-
//! @param overwrite
-
//! Specifies how to handle the situation if the destination already
-
//! exists. See the @[Overwrite] type for details.
-
//!
-
//! @param result
-
//! A @[MultiStatus.Prefixed] to collect status mappings if some
-
//! subparts fail. It's prefixed with the URL to the filesystem
-
//! location.
-
//!
-
//! @returns
-
//! Returns a 2xx series status mapping on success (typically 201
-
//! Created if the destination didn't exist before, or 204 No
-
//! Content otherwise). Returns 0 if the source doesn't exist.
-
//! Returns an appropriate status mapping for any other error. That
-
//! includes an empty mapping in case there's a failure on some
-
//! subpart or at the destination, to signify a 207 Multi-Status
-
//! response using the info in @[id->get_multi_status()].
-
protected mapping(string:mixed) copy_collection(
-
string source, string destination, PropertyBehavior behavior,
-
Overwrite overwrite, MultiStatus.Prefixed result, RequestID id)
-
{
-
SIMPLE_TRACE_ENTER(this, "copy_collection(%O, %O, %O, %O, %O, %O).",
-
source, destination, behavior, overwrite, result, id);
-
Stat st = stat_file(destination, id);
-
if (st) {
-
// Destination exists. Check the overwrite header.
-
switch(overwrite) {
-
case DO_OVERWRITE:
-
// RFC 2518 8.8.4
-
// If a resource exists at the destination, and the Overwrite
-
// header is "T" then prior to performing the copy the server
-
// MUST perform a DELETE with "Depth: infinity" on the
-
// destination resource.
-
TRACE_ENTER("Destination exists and overwrite is on.", this);
-
mapping(string:mixed) res = recurse_delete_files(destination, id, result);
-
if (res && (!sizeof (res) || res->error >= 300)) {
-
// Failed to delete something.
-
TRACE_LEAVE("Deletion failed.");
-
TRACE_LEAVE("Copy collection failed.");
-
// RFC 2518 9.6 says:
-
//
-
// If a COPY or MOVE is not performed due to the value of
-
// the Overwrite header, the method MUST fail with a 412
-
// (Precondition Failed) status code.
-
//
-
// That can perhaps be interpreted as that we should return
-
// 412 here. But otoh, in RFC 2518 8.8.5 COPY status codes:
-
//
-
// 412 (Precondition Failed) - /.../ the Overwrite header
-
// is "F" and the state of the destination resource is
-
// non-null.
-
//
-
// That clearly doesn't include this case. Also, common sense
-
// says that the error from the failed delete is more useful
-
// to the client.
-
#if 0
-
return Roxen.http_status(Protocols.HTTP.HTTP_PRECOND_FAILED);
-
#else
-
if (sizeof (res)) {
-
// RFC 2518 8.8.3:
-
// If an error in executing the COPY method occurs with a
-
// resource other than the resource identified in the
-
// Request-URI then the response MUST be a 207
-
// (Multi-Status).
-
//
-
// So if the failure was on the root destination resource we
-
// have to convert it to a multi-status.
-
result->add_status (destination, res->error, res->rettext);
-
}
-
return ([]);
-
#endif
-
}
-
TRACE_LEAVE("Deletion ok.");
-
break;
-
case NEVER_OVERWRITE:
-
TRACE_LEAVE("Destination already exists.");
-
return Roxen.http_status(Protocols.HTTP.HTTP_PRECOND_FAILED);
-
case MAYBE_OVERWRITE:
-
// No overwrite header.
-
// Be nice, and fail only if we don't already have a collection.
-
if (st->isdir) {
-
TRACE_LEAVE("Destination exists and is a directory.");
-
return copy_properties(source, destination, behavior, id);
-
}
-
TRACE_LEAVE("Destination exists and is not a directory.");
-
return Roxen.http_status(Protocols.HTTP.HTTP_PRECOND_FAILED);
-
}
-
}
-
// Create the new collection.
-
TRACE_LEAVE("Make a new collection.");
-
mapping(string:mixed) res = make_collection(destination, id);
-
if (res && res->error >= 300) return res;
-
return copy_properties(source, destination, behavior, id) || res;
-
}
-
-
//! Used by the default @[recurse_copy_files] to copy a single file
-
//! along with its properties.
-
//!
-
//! @param source
-
//! Source path below the filesystem location.
-
//!
-
//! @param destination
-
//! Destination path below the filesystem location.
-
//!
-
//! @param behavior
-
//! Specifies how to copy properties. See the @[PropertyBehavior]
-
//! type for details.
-
//!
-
//! @param overwrite
-
//! Specifies how to handle the situation if the destination already
-
//! exists. See the @[Overwrite] type for details.
-
//!
-
//! @returns
-
//! Returns a 2xx series status mapping on success (typically 201
-
//! Created if the destination didn't exist before, or 204 No
-
//! Content otherwise). Returns 0 if the source doesn't exist.
-
//! Returns an appropriate status mapping for any other error.
-
protected mapping(string:mixed) copy_file(string source, string destination,
-
PropertyBehavior behavior,
-
Overwrite overwrite, RequestID id)
-
{
-
SIMPLE_TRACE_ENTER(this, "copy_file(%O, %O, %O, %O, %O)\n",
-
source, destination, behavior, overwrite, id);
-
TRACE_LEAVE("Not implemented.");
-
return Roxen.http_status (Protocols.HTTP.HTTP_NOT_IMPL);
-
}
-
-
//! Copy a resource recursively from @[source] to @[destination].
-
//!
-
//! @param source
-
//! Source path below the filesystem location.
-
//!
-
//! @param destination
-
//! Destination path below the filesystem location.
-
//!
-
//! @param behavior
-
//! Specifies how to copy properties. See the @[PropertyBehavior]
-
//! type for details.
-
//!
-
//! @param overwrite
-
//! Specifies how to handle the situation if the destination already
-
//! exists. See the @[Overwrite] type for details.
-
//!
-
//! @returns
-
//! Returns a 2xx series status mapping on success (typically 201
-
//! Created if the destination didn't exist before, or 204 No
-
//! Content otherwise). Returns 0 if the source doesn't exist.
-
//! Returns an appropriate status mapping for any other error. That
-
//! includes an empty mapping in case there's a failure on some
-
//! subpart or at the destination, to signify a 207 Multi-Status
-
//! response using the info in @[id->get_multi_status()].
-
mapping(string:mixed) recurse_copy_files(string source, string destination,
-
PropertyBehavior behavior,
-
Overwrite overwrite, RequestID id)
-
{
-
SIMPLE_TRACE_ENTER(this, "Recursive copy from %O to %O (%s)",
-
source, destination,
-
overwrite == DO_OVERWRITE ? "replace" :
-
overwrite == NEVER_OVERWRITE ? "no overwrite" :
-
"overlay");
-
string src_tmp = has_suffix(source, "/")?source:(source+"/");
-
string dst_tmp = has_suffix(destination, "/")?destination:(destination+"/");
-
if ((src_tmp == dst_tmp) ||
-
has_prefix(src_tmp, dst_tmp) ||
-
has_prefix(dst_tmp, src_tmp)) {
-
TRACE_LEAVE("Source and destination overlap.");
-
return Roxen.http_status(403, "Source and destination overlap.");
-
}
-
-
string prefix = map(query_location()[1..]/"/", Roxen.http_encode_url)*"/";
-
MultiStatus.Prefixed result =
-
id->get_multi_status()->prefix (id->url_base() + prefix);
-
-
mapping(string:mixed) recurse(string source, string destination) {
-
// Note: Already got an extra TRACE_ENTER level on entry here.
-
-
Stat st = stat_file(source, id);
-
if (!st) {
-
TRACE_LEAVE("Source not found.");
-
return 0;
-
}
-
// FIXME: Check destination?
-
if (st->isdir) {
-
mapping(string:mixed) res =
-
copy_collection(source, destination, behavior, overwrite, result, id);
-
if (res && (!sizeof (res) || res->error >= 300)) {
-
// RFC 2518 8.8.3 and 8.8.8 (error minimization).
-
TRACE_LEAVE("Copy of collection failed.");
-
return res;
-
}
-
foreach(find_dir(source, id), string filename) {
-
string subsrc = combine_path_unix(source, filename);
-
string subdst = combine_path_unix(destination, filename);
-
SIMPLE_TRACE_ENTER(this, "Copy from %O to %O\n", subsrc, subdst);
-
mapping(string:mixed) sub_res = recurse(subsrc, subdst);
-
if (sub_res && !(<0, 201, 204>)[sub_res->error]) {
-
result->add_status(subdst, sub_res->error, sub_res->rettext);
-
}
-
}
-
TRACE_LEAVE("");
-
return res;
-
} else {
-
TRACE_LEAVE("");
-
return copy_file(source, destination, behavior, overwrite, id);
-
}
-
};
-
-
int start_ms_size = id->multi_status_size();
-
mapping(string:mixed) res = recurse (source, destination);
-
if (res && res->error != 204 && res->error != 201)
-
return res;
-
else if (id->multi_status_size() != start_ms_size)
-
return ([]);
-
else
-
return res;
-
}
-
-
//! Used by the default @[recurse_move_files] to move a file (and not
-
//! a directory) from @[source] to @[destination].
-
//!
-
//! The default implementation tries to call @[find_file] with the
-
//! MOVE method. If that returns 501 Not Implemented then it copies
-
//! the file and deletes the source afterwards.
-
//!
-
//! @param source
-
//! Source path below the filesystem location.
-
//!
-
//! @param destination
-
//! Destination path below the filesystem location.
-
//!
-
//! @param behavior
-
//! Specifies how to move properties. See the @[PropertyBehavior]
-
//! type for details.
-
//!
-
//! @param overwrite
-
//! Specifies how to handle the situation if the destination already
-
//! exists. See the @[Overwrite] type for details.
-
//!
-
//! @returns
-
//! Returns a 2xx series status mapping on success (typically 201
-
//! Created if the destination didn't exist before, or 204 No
-
//! Content otherwise). Returns 0 if the source doesn't exist.
-
//! Returns an appropriate status mapping for any other error. That
-
//! includes an empty mapping in case there's a failure on some
-
//! subpart or at the destination, to signify a 207 Multi-Status
-
//! response using the info in @[id->get_multi_status()].
-
protected mapping(string:mixed) move_file(string source, string destination,
-
PropertyBehavior behavior,
-
Overwrite overwrite, RequestID id)
-
{
-
// Fall back to find_file().
-
RequestID tmp_id = id->clone_me();
-
tmp_id->not_query = query_location() + source;
-
tmp_id->misc["new-uri"] = query_location() + destination;
-
tmp_id->request_headers->destination =
-
id->url_base() + query_location()[1..] + destination;
-
tmp_id->method = "MOVE";
-
mapping(string:mixed) res = find_file(source, tmp_id);
-
if (!res || res->error != 501) return res;
-
// Not implemented. Fall back to COPY + DELETE.
-
res = copy_file(source, destination, behavior, overwrite, id);
-
if (res && res->error >= 300) {
-
// Copy failed.
-
return res;
-
}
-
return delete_file(source, id);
-
}
-
-
//! Used by the default @[recurse_move_files] to move a collection
-
//! (aka directory) and all its content from @[source] to
-
//! @[destination].
-
//!
-
//! The default implementation tries to call @[find_file] with the
-
//! MOVE method. If that returns 501 Not Implemented then it copies
-
//! the collection, moves each directory entry recursively, and
-
//! deletes the source collection afterwards.
-
//!
-
//! @param source
-
//! Source path below the filesystem location.
-
//!
-
//! @param destination
-
//! Destination path below the filesystem location.
-
//!
-
//! @param behavior
-
//! Specifies how to move properties. See the @[PropertyBehavior]
-
//! type for details.
-
//!
-
//! @param overwrite
-
//! Specifies how to handle the situation if the destination already
-
//! exists. See the @[Overwrite] type for details.
-
//!
-
//! @returns
-
//! Returns a 2xx series status mapping on success (typically 201
-
//! Created if the destination didn't exist before, or 204 No
-
//! Content otherwise). Returns 0 if the source doesn't exist.
-
//! Returns an appropriate status mapping for any other error. That
-
//! includes an empty mapping in case there's a failure on some
-
//! subpart or at the destination, to signify a 207 Multi-Status
-
//! response using the info in @[id->get_multi_status()].
-
//!
-
//! @note
-
//! The function must be prepared to recurse to check DAV locks
-
//! properly.
-
protected mapping(string:mixed) move_collection(
-
string source, string destination, PropertyBehavior behavior,
-
Overwrite overwrite, RequestID id)
-
{
-
// Fall back to find_file().
-
RequestID tmp_id = id->clone_me();
-
tmp_id->not_query = query_location() + source;
-
tmp_id->misc["new-uri"] = query_location() + destination;
-
tmp_id->request_headers->destination =
-
id->url_base() + query_location()[1..] + destination;
-
tmp_id->method = "MOVE";
-
mapping(string:mixed) res = find_file(source, tmp_id);
-
if (!res || res->error != 501) return res;
-
// Not implemented. Fall back to COPY + DELETE.
-
string prefix = map(query_location()[1..]/"/", Roxen.http_encode_url)*"/";
-
MultiStatus.Prefixed result =
-
id->get_multi_status()->prefix (id->url_base() + prefix);
-
res = copy_collection(source, destination, behavior, overwrite, result, id);
-
if (res && (res->error >= 300 || !sizeof(res))) {
-
// Copy failed.
-
return res;
-
}
-
int fail;
-
foreach(find_dir(source, id), string filename) {
-
string subsrc = combine_path_unix(source, filename);
-
string subdst = combine_path_unix(destination, filename);
-
SIMPLE_TRACE_ENTER(this, "Recursive move from %O to %O\n",
-
subsrc, subdst);
-
if (mapping(string:mixed) sub_res =
-
recurse_move_files(subsrc, subdst, behavior, overwrite, id)) {
-
if (!(<0, 201, 204>)[sub_res->error]) {
-
result->add_status(subdst, sub_res->error, sub_res->rettext);
-
}
-
if (!sizeof (sub_res) || sub_res->error >= 300) {
-
// Failed to move some content.
-
fail = 1;
-
}
-
}
-
}
-
if (fail) return ([]);
-
return delete_file(source, id);
-
}
-
-
//! Move a resource from @[source] to @[destination].
-
//!
-
//! @param source
-
//! Source path below the filesystem location.
-
//!
-
//! @param destination
-
//! Destination path below the filesystem location.
-
//!
-
//! @param behavior
-
//! Specifies how to move properties. See the @[PropertyBehavior]
-
//! type for details.
-
//!
-
//! @param overwrite
-
//! Specifies how to handle the situation if the destination already
-
//! exists. See the @[Overwrite] type for details.
-
//!
-
//! @returns
-
//! Returns a 2xx series status mapping on success (typically 201
-
//! Created if the destination didn't exist before, or 204 No
-
//! Content otherwise). Returns 0 if the source doesn't exist.
-
//! Returns an appropriate status mapping for any other error. That
-
//! includes an empty mapping in case there's a failure on some
-
//! subpart or at the destination, to signify a 207 Multi-Status
-
//! response using the info in @[id->get_multi_status()].
-
mapping(string:mixed) recurse_move_files(string source, string destination,
-
PropertyBehavior behavior,
-
Overwrite overwrite, RequestID id)
-
{
-
Stat st = stat_file(source, id);
-
if (!st) return 0;
-
-
if (st->isdir) {
-
return move_collection(source, destination, behavior, overwrite, id);
-
}
-
return move_file(source, destination, behavior, overwrite, id);
-
}
-
-
string real_file(string f, RequestID id){}
-
-
void add_api_function( string name, function f, void|array(string) types)
-
{
-
_api_functions[name] = ({ f, types });
-
}
-
-
mapping api_functions()
-
{
-
return _api_functions;
-
}
-
-
#if ROXEN_COMPAT <= 1.4
-
mapping(string:function) query_tag_callers()
-
//! Compat
-
{
-
mapping(string:function) m = ([]);
-
foreach(glob("tag_*", indices( this_object())), string q)
-
if(functionp( this_object()[q] ))
-
m[replace(q[4..], "_", "-")] = this_object()[q];
-
return m;
-
}
-
-
mapping(string:function) query_container_callers()
-
//! Compat
-
{
-
mapping(string:function) m = ([]);
-
foreach(glob("container_*", indices( this_object())), string q)
-
if(functionp( this_object()[q] ))
-
m[replace(q[10..], "_", "-")] = this_object()[q];
-
return m;
-
}
-
#endif
-
-
mapping(string:array(int|function)) query_simpletag_callers()
-
{
-
mapping(string:array(int|function)) m = ([]);
-
foreach(glob("simpletag_*", indices(this_object())), string q)
-
if(functionp(this_object()[q]))
-
m[replace(q[10..],"_","-")] =
-
({ intp (this_object()[q + "_flags"]) && this_object()[q + "_flags"],
-
this_object()[q] });
-
return m;
-
}
-
-
mapping(string:array(int|function)) query_simple_pi_tag_callers()
-
{
-
mapping(string:array(int|function)) m = ([]);
-
foreach (glob ("simple_pi_tag_*", indices (this_object())), string q)
-
if (functionp (this_object()[q]))
-
m[replace (q[sizeof ("simple_pi_tag_")..], "_", "-")] =
-
({(intp (this_object()[q + "_flags"]) && this_object()[q + "_flags"]) |
-
RXML.FLAG_PROC_INSTR, this_object()[q]});
-
return m;
-
}
-
-
RXML.TagSet query_tag_set()
-
{
-
if (!module_tag_set) {
-
array(function|program|object) tags =
-
filter (rows (this_object(),
-
glob ("Tag*", indices (this_object()))),
-
lambda(mixed x) { return functionp(x)||programp(x); });
-
for (int i = 0; i < sizeof (tags); i++)
-
if (programp (tags[i]))
-
if (!tags[i]->is_RXML_Tag) tags[i] = 0;
-
else tags[i] = tags[i]();
-
else {
-
tags[i] = tags[i]();
-
// Bogosity: The check is really a little too late here..
-
if (!tags[i]->is_RXML_Tag) tags[i] = 0;
-
}
-
tags -= ({0});
-
module_tag_set =
-
(this_object()->ModuleTagSet || RXML.TagSet) (this_object(), "", tags);
-
}
-
return module_tag_set;
-
}
-
-
mixed get_value_from_file(string path, string index, void|string pre)
-
{
-
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
-
//!
-
{
-
return get_my_sql()->query( replace( query, __my_tables ), @args );
-
}
-
-
object sql_big_query( string query, mixed ... args )
-
//! Identical to @[sql_query], but the @[Sql.Sql()->big_query] method
-
//! will be used instead of the @[Sql.Sql()->query] method.
-
{
-
return get_my_sql()->big_query( replace( query, __my_tables ), @args );
-
}
-
-
array(mapping(string:mixed)) sql_query_ro( string query, mixed ... args )
-
//! Do a read-only 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
-
//!
-
{
-
return get_my_sql(1)->query( replace( query, __my_tables ), @args );
-
}
-
-
object sql_big_query_ro( string query, mixed ... args )
-
//! Identical to @[sql_query_ro], but the @[Sql.Sql()->big_query] method
-
//! will be used instead of the @[Sql.Sql()->query] method.
-
{
-
return get_my_sql(1)->big_query( replace( query, __my_tables ), @args );
-
}
-
-
protected int create_sql_tables( mapping(string:array(string)) definitions,
-
string|void comment, int|void no_unique_names )
-
//! Create multiple tables in one go. See @[get_my_table]
-
//! Returns the number of tables that were actually created.
-
{
-
int ddc;
-
if( !no_unique_names )
-
foreach( indices( definitions ), string t )
-
ddc+=get_my_table( t, definitions[t], comment, 1 );
-
else
-
{
-
Sql.Sql sql = get_my_sql();
-
foreach( indices( definitions ), string t )
-
{
-
if( !catch {
-
sql->query("CREATE TABLE "+t+" ("+definitions[t]*","+")" );
-
} )
-
ddc++;
-
DBManager.is_module_table( this_object(), my_db, t, comment );
-
}
-
}
-
return ddc;
-
}
-
-
protected string sql_table_exists( string name )
-
//! Return the real name of the table 'name' if it exists.
-
{
-
if(strlen(name))
-
name = "_"+name;
-
-
string res = hash(_my_configuration->name)->digits(36)
-
+ "_" + replace(sname(), ({ "#","-" }), ({ "_","_" })) + name;
-
-
return catch(get_my_sql()->query( "SELECT * FROM "+res+" LIMIT 1" ))?0:res;
-
}
-
-
-
protected string|int get_my_table( string|array(string) name,
-
void|array(string)|string definition,
-
string|void comment, int|void flag )
-
//! @decl string get_my_table( string name, array(string) types )
-
//! @decl string get_my_table( string name, string definition )
-
//! @decl string get_my_table( string definition )
-
//! @decl string get_my_table( array(string) definition )
-
//!
-
//! Returns the name of a table in the 'shared' database that is
-
//! unique for this module. It is possible to select another database
-
//! by using @[set_my_db] before calling this function.
-
//!
-
//! You can use @[create_sql_tables] instead of this function if you want
-
//! to create more than one table in one go.
-
//!
-
//! If @[flag] is true, return 1 if a table was created, and 0 otherwise.
-
//!
-
//! In the first form, @[name] is the (postfix of) the name of the
-
//! table, and @[types] is an array of definitions, as an example:
-
//!
-
//!
-
//! @code
-
//! cache_table = get_my_table( "cache", ({
-
//! "id INT UNSIGNED AUTO_INCREMENT",
-
//! "data BLOB",
-
//! }) );
-
//! @endcode
-
//!
-
//! In the second form, the whole table definition is instead sent as
-
//! a string. The cases where the name is not included (the third and
-
//! fourth form) is equivalent to the first two cases with the name ""
-
//!
-
//! If the table does not exist in the datbase, it is created.
-
//!
-
//! @note
-
//! This function may not be called from create
-
//
-
// If it exists, but it's definition is different, the table will be
-
// altered with a ALTER TABLE call to conform to the definition. This
-
// might not work if the database the table resides in is not a MySQL
-
// database (normally it is, but it's possible, using @[set_my_db],
-
// to change this).
-
{
-
string oname;
-
int ddc;
-
if( !definition )
-
{
-
definition = name;
-
oname = name = "";
-
}
-
else if(strlen(name))
-
name = "_"+(oname = name);
-
-
Sql.Sql sql = get_my_sql();
-
-
string res = hash(_my_configuration->name)->digits(36)
-
+ "_" + replace(sname(),({ "#","-" }), ({ "_","_" })) + name;
-
-
if( !sql )
-
{
-
report_error("Failed to get SQL handle, permission denied for "+my_db+"\n");
-
return 0;
-
}
-
if( arrayp( definition ) )
-
definition *= ", ";
-
-
if( catch(sql->query( "SELECT * FROM "+res+" LIMIT 1" )) )
-
{
-
ddc++;
-
mixed error =
-
catch
-
{
-
get_my_sql()->query( "CREATE TABLE "+res+" ("+definition+")" );
-
DBManager.is_module_table( this_object(), my_db, res,
-
oname+"\0"+comment );
-
};
-
if( error )
-
{
-
if( strlen( name ) )
-
name = " "+name;
-
report_error( "Failed to create table"+name+": "+
-
describe_error( error ) );
-
return 0;
-
}
-
if( flag )
-
{
-
__my_tables[ "&"+oname+";" ] = res;
-
return ddc;
-
}
-
return __my_tables[ "&"+oname+";" ] = res;
-
}
-
// // Update definition if it has changed.
-
// mixed error =
-
// catch
-
// {
-
// get_my_sql()->query( "ALTER TABLE "+res+" ("+definition+")" );
-
// };
-
// if( error )
-
// {
-
// if( strlen( name ) )
-
// name = " for "+name;
-
// report_notice( "Failed to update table definition"+name+": "+
-
// describe_error( error ) );
-
// }
-
if( flag )
-
{
-
__my_tables[ "&"+oname+";" ] = res;
-
return ddc;
-
}
-
return __my_tables[ "&"+oname+";" ] = res;
-
}
-
-
protected string my_db = "local";
-
-
protected void set_my_db( string to )
-
//! Select the database in which tables will be created with
-
//! get_my_table, and also the one that will be returned by
-
//! @[get_my_sql]
-
{
-
my_db = to;
-
}
-
-
Sql.Sql get_my_sql( int|void read_only, void|string charset )
-
//! 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);
+
Newline at end of file removed.