835c6c2001-06-17Martin Nilsson // This file is part of Roxen WebServer.
f41b982009-05-07Martin Stjernholm // Copyright © 1996 - 2009, Roxen IS.
0917d32013-03-04Anders Johansson // $Id$
c20c872000-02-16Per Hedbor  #include <module_constants.h>
b1fca01996-11-12Per Hedbor #include <module.h>
c5e0961999-10-04Per Hedbor #include <request_trace.h>
b275871998-05-23Henrik Grubbström (Grubba) 
c089042001-06-11Per Hedbor constant __pragma_save_parent__ = 1;
32cd1c2008-10-13Martin Stjernholm // Tell Pike.count_memory this is global. constant pike_cycle_depth = 0;
a59d252000-07-04Per Hedbor inherit "basic_defvar";
9d9b9b1999-11-17Per Hedbor mapping(string:array(int)) error_log=([]);
c5e0961999-10-04Per Hedbor 
9d9b9b1999-11-17Per Hedbor constant is_module = 1;
a730c22001-01-19Per Hedbor // constant module_type = MODULE_ZERO; // constant module_name = "Unnamed module"; // constant module_doc = "Undocumented"; constant module_unique = 1;
c5e0961999-10-04Per Hedbor 
b8f6272010-06-28Martin Jonsson //! 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;
a59d252000-07-04Per Hedbor 
7211372001-06-29Martin Stjernholm private Configuration _my_configuration;
0aa37d2001-08-23Martin Stjernholm private string _module_local_identifier;
2e8d0f2001-06-28Martin Stjernholm private string _module_identifier =
7211372001-06-29Martin Stjernholm  lambda() {
3557f52001-06-30Martin Stjernholm  mixed init_info = roxen->bootstrap_info->get(); if (arrayp (init_info)) {
0aa37d2001-08-23Martin Stjernholm  [_my_configuration, _module_local_identifier] = init_info; return _my_configuration->name + "/" + _module_local_identifier;
7211372001-06-29Martin Stjernholm  }
0e7d592009-11-01Martin Stjernholm #ifdef DEBUG else error ("Got invalid bootstrap info for module: %O\n", init_info); #endif
7211372001-06-29Martin Stjernholm  }();
fc40392008-08-15Martin Stjernholm protected mapping _api_functions = ([]);
a59d252000-07-04Per Hedbor 
467a8a2015-10-16Marcus Agehall 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;
a59d252000-07-04Per Hedbor string|array(string) module_creator; string module_url; RXML.TagSet module_tag_set;
5940a52006-10-13Martin Stjernholm /* 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. :-)
c20c872000-02-16Per Hedbor  */
b242582009-04-15Jonas Wallden 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); }
fe55e82012-07-02Martin Stjernholm 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);}
c20c872000-02-16Per Hedbor 
5940a52006-10-13Martin Stjernholm 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); }
c20c872000-02-16Per Hedbor 
467a8a2015-10-16Marcus Agehall 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); } }
b9ec252000-01-05Martin Stjernholm string module_identifier()
0aa37d2001-08-23Martin Stjernholm //! 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.
b9ec252000-01-05Martin Stjernholm { return _module_identifier; }
0aa37d2001-08-23Martin Stjernholm 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; }
298be12001-08-13Per Hedbor RoxenModule this_module() { return this_object(); // To be used from subclasses. }
2ca4282009-11-24Stefan Wallström //! @ignore
d584002009-10-31Martin Stjernholm DECLARE_OBJ_COUNT;
2ca4282009-11-24Stefan Wallström //! @endignore
d584002009-10-31Martin Stjernholm 
b6fb051999-11-02Per Hedbor string _sprintf() {
d584002009-10-31Martin Stjernholm  return sprintf ("RoxenModule(%s)" + OBJ_COUNT, _module_identifier || "?");
b6fb051999-11-02Per Hedbor }
c5e0961999-10-04Per Hedbor array register_module() { return ({
a730c22001-01-19Per Hedbor  this_object()->module_type,
ad56072001-01-29Per Hedbor  this_object()->module_name, this_object()->module_doc,
c5e0961999-10-04Per Hedbor  0, module_unique,
5b81122002-02-26Marcus Wellhardh  this_object()->module_locked,
7500892008-03-17Henrik Grubbström (Grubba)  this_object()->module_counter,
c5e0961999-10-04Per Hedbor  }); }
2a2a5b1996-12-01Per Hedbor string fix_cvs(string from) {
fd0b6f1996-12-02Per Hedbor  from = replace(from, ({ "$", "Id: "," Exp $" }), ({"","",""}));
2a2a5b1996-12-01Per Hedbor  sscanf(from, "%*s,v %s", from);
00730e1999-11-18Per Hedbor  return replace(from,"/","-");
2a2a5b1996-12-01Per Hedbor }
72ac572000-01-31Per Hedbor int module_dependencies(Configuration configuration,
f5a2741999-10-18Per Hedbor  array (string) modules, int|void now)
c4d7ec2008-05-21Martin Stjernholm //! 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.
edb5061997-08-25Peter Bortas {
cb9c222000-09-06Martin Stjernholm  modules = map (modules, lambda (string modname) { sscanf ((modname / "/")[-1], "%[^#]", modname); return modname; }); Configuration conf = configuration || my_configuration(); if (!conf)
28928d2005-10-06Marcus Wellhardh  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");
cb9c222000-09-06Martin Stjernholm  else conf->add_modules( modules, now );
edb5061997-08-25Peter Bortas  return 1; }
2a2a5b1996-12-01Per Hedbor string file_name_and_stuff() {
a59d252000-07-04Per Hedbor  return ("<b>Loaded from:</b> "+(roxen->filename(this_object()))+"<br>"+ (this_object()->cvs_version?
80cd682003-11-17Anders Johansson  "<b>CVS Version:</b> "+
a59d252000-07-04Per Hedbor  fix_cvs(this_object()->cvs_version)+"\n":""));
2a2a5b1996-12-01Per Hedbor }
5839c31999-01-22Marcus Comstedt 
9f2a971999-11-29Per Hedbor Configuration my_configuration()
facee72000-07-18Johan Sundström //! Returns the Configuration object of the virtual server the module //! belongs to.
b1fca01996-11-12Per Hedbor {
2e8d0f2001-06-28Martin Stjernholm  return _my_configuration;
b1fca01996-11-12Per Hedbor }
fc40392008-08-15Martin Stjernholm final void set_configuration(Configuration c)
5839c31999-01-22Marcus Comstedt { if(_my_configuration && _my_configuration != c) error("set_configuration() called twice.\n"); _my_configuration = c;
467a8a2015-10-16Marcus Agehall  // if configuration changes, we should reinitialize our JSON logger too! json_logger = ModuleJSONLogger(_my_configuration);
5839c31999-01-22Marcus Comstedt }
48679b2000-02-03Johan Sundström void set_module_creator(string|array(string) c)
facee72000-07-18Johan Sundström //! 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.
b1fca01996-11-12Per Hedbor { module_creator = c; } void set_module_url(string to)
facee72000-07-18Johan Sundström //! 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.
b1fca01996-11-12Per Hedbor { module_url = to; } void free_some_sockets_please(){}
4cb9c92007-07-12Martin Stjernholm // 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.)
4ea8652006-10-13Martin Stjernholm void start(int variable_save, Configuration conf, void|int newly_added) {}
4cb9c92007-07-12Martin Stjernholm void stop() {} void ready_to_receive_requests (Configuration conf) {}
b1fca01996-11-12Per Hedbor 
a59d252000-07-04Per Hedbor string status() {}
c5e0961999-10-04Per Hedbor 
7fb7f51999-11-29Per Hedbor string info(Configuration conf)
72ac572000-01-31Per Hedbor {
facee72000-07-18Johan Sundström  return (this_object()->register_module()[2]);
df6cd11998-10-13Per Hedbor }
b1fca01996-11-12Per Hedbor 
a730c22001-01-19Per Hedbor string sname( ) {
4a50182001-01-30Per Hedbor  return my_configuration()->otomod[ this_object() ];
a730c22001-01-19Per Hedbor }
2f4ac12000-11-02Per Hedbor ModuleInfo my_moduleinfo( )
6533f22001-08-23Martin Nilsson //! Returns the associated @[ModuleInfo] object
2f4ac12000-11-02Per Hedbor {
a730c22001-01-19Per Hedbor  string f = sname();
2f4ac12000-11-02Per Hedbor  if( f ) return roxen.find_module( (f/"#")[0] ); }
2c13a81999-11-10Per Hedbor void save_me()
e7e6031999-11-05Per Hedbor { my_configuration()->save_one( this_object() );
2f4ac12000-11-02Per Hedbor  my_configuration()->module_changed( my_moduleinfo(), this_object() );
e7e6031999-11-05Per Hedbor }
1e9a1b2001-02-21Per Hedbor void save() { save_me(); } string comment() { return ""; }
b1fca01996-11-12Per Hedbor 
5839c31999-01-22Marcus Comstedt string query_internal_location()
facee72000-07-18Johan Sundström //! Returns the internal mountpoint, where <ref>find_internal()</ref>
ec32302010-04-27Martin Stjernholm //! is mounted. It always ends with a '/'.
5839c31999-01-22Marcus Comstedt { if(!_my_configuration) error("Please do not call this function from create()!\n"); return _my_configuration->query_internal_location(this_object()); }
525f722000-12-05Martin Nilsson string query_absolute_internal_location(RequestID id)
ec32302010-04-27Martin Stjernholm //! Returns the internal mountpoint as an absolute path. It always //! ends with a '/'.
525f722000-12-05Martin Nilsson { return (id->misc->site_prefix_path || "") + query_internal_location(); }
b7c45e1997-01-27Per Hedbor string query_location()
1e9a1b2001-02-21Per Hedbor //! Returns the mountpoint as an absolute path. The default //! implementation uses the "location" configuration variable in the //! module.
b7c45e1997-01-27Per Hedbor { string s; catch{s = query("location");}; return s; }
162bd52000-02-20Martin Stjernholm array(string) location_urls()
facee72000-07-18Johan Sundström //! Returns an array of all locations where the module is mounted.
162bd52000-02-20Martin Stjernholm { string loc = query_location(); if (!loc) return ({}); if(!_my_configuration) error("Please do not call this function from create()!\n");
3adf202000-03-06Jonas Wallden  array(string) urls = copy_value(_my_configuration->query("URLs"));
7984d62000-10-06Martin Stjernholm  string hostname;
b0a36c2008-12-11Jonas Wallden  if (string world_url = _my_configuration->query ("MyWorldLocation")) if (sizeof(world_url)) { Standards.URI uri = Standards.URI(world_url); hostname = uri->host; }
7984d62000-10-06Martin Stjernholm  if (!hostname) hostname = gethostname(); for (int i = 0; i < sizeof (urls); i++)
21182a2001-10-05Per Hedbor  { urls[i] = (urls[i]/"#")[0];
7984d62000-10-06Martin Stjernholm  if (sizeof (urls[i]/"*") == 2)
162bd52000-02-20Martin Stjernholm  urls[i] = replace(urls[i], "*", hostname);
21182a2001-10-05Per Hedbor  }
162bd52000-02-20Martin Stjernholm  return map (urls, `+, loc[1..]); }
3423432005-04-22Marcus Wellhardh 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");
b0a36c2008-12-11Jonas Wallden  if (world_url && sizeof(world_url)) {
dc89282008-12-11Jonas Wallden  Standards.URI uri = Standards.URI(world_url); hostname = uri->host; }
3423432005-04-22Marcus Wellhardh  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);
b698792009-07-07Jonas Wallden  string ip = p->ip || "127.0.0.1"; if (ip == "::") ip = "::1"; uri->fragment = "ip=" + ip;
3423432005-04-22Marcus Wellhardh  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; }
69aba82013-07-10Anders Johansson  uri->path += loc[1..]; return (string)uri;
3423432005-04-22Marcus Wellhardh  } 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));
69aba82013-07-10Anders Johansson  candidate_uri->path += loc[1..]; return (string)candidate_uri;
3423432005-04-22Marcus Wellhardh  } return 0; }
ae32d01998-03-23David Hedbor /* By default, provide nothing. */
ea5df52005-02-25Henrik Grubbström (Grubba) multiset(string) query_provides() { return 0; }
b7c45e1997-01-27Per Hedbor 
bc0fa02001-03-08Per Hedbor function(RequestID:int|mapping) query_seclevels()
b1fca01996-11-12Per Hedbor {
a59d252000-07-04Per Hedbor  if(catch(query("_seclevels")) || (query("_seclevels") == 0))
bc0fa02001-03-08Per Hedbor  return 0; return roxen.compile_security_pattern(query("_seclevels"),this_object());
b1fca01996-11-12Per Hedbor }
c6ce732004-05-07Martin Stjernholm 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
0c5a852004-05-12Martin Stjernholm //! Path (below the filesystem location) to which the status applies.
c6ce732004-05-07Martin Stjernholm //! //! @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); }
8b8ecf2000-08-28Johan Sundström 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)
a476711997-10-20Henrik Grubbström (Grubba) {
ca28e82004-03-23Martin Stjernholm  SIMPLE_TRACE_ENTER(this, "find_dir_stat(): %O", f);
b275871998-05-23Henrik Grubbström (Grubba) 
a476711997-10-20Henrik Grubbström (Grubba)  array(string) files = find_dir(f, id);
8b8ecf2000-08-28Johan Sundström  mapping(string:Stat) res = ([]);
a476711997-10-20Henrik Grubbström (Grubba) 
0c8b9a1997-10-22Henrik Grubbström (Grubba)  foreach(files || ({}), string fname) {
ca28e82004-03-23Martin Stjernholm  SIMPLE_TRACE_ENTER(this, "stat()'ing %O", f + "/" + fname);
c0f4542001-02-19Jonas Wallden  Stat st = stat_file(replace(f + "/" + fname, "//", "/"), id);
a476711997-10-20Henrik Grubbström (Grubba)  if (st) {
0c8b9a1997-10-22Henrik Grubbström (Grubba)  res[fname] = st;
b275871998-05-23Henrik Grubbström (Grubba)  TRACE_LEAVE("OK"); } else { TRACE_LEAVE("No stat info");
a476711997-10-20Henrik Grubbström (Grubba)  } }
b275871998-05-23Henrik Grubbström (Grubba)  TRACE_LEAVE("");
a476711997-10-20Henrik Grubbström (Grubba)  return(res); }
a59d252000-07-04Per Hedbor 
d35dfd2004-04-20Martin Stjernholm class DefaultPropertySet { inherit PropertySet;
fc40392008-08-15Martin Stjernholm  protected Stat stat;
7c83872004-04-28Martin Stjernholm 
fc40392008-08-15Martin Stjernholm  protected void create (string path, string abs_path, RequestID id, Stat stat)
7c83872004-04-28Martin Stjernholm  {
2ece722004-05-06Henrik Grubbström (Grubba)  ::create (path, abs_path, id);
7c83872004-04-28Martin Stjernholm  this_program::stat = stat; } Stat get_stat() {return stat;}
fc40392008-08-15Martin Stjernholm  protected mapping(string:string) response_headers;
d35dfd2004-04-20Martin Stjernholm  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;
ba25d22012-09-24Henrik Grubbström (Grubba)  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..]) * "?"; }
d35dfd2004-04-20Martin Stjernholm  sub_id->method = "HEAD";
7ff7602004-04-28Martin Stjernholm  mapping(string:mixed)|int(-1..0)|object res = find_file (path, sub_id);
d35dfd2004-04-20Martin Stjernholm  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; } }
c5dd9f2004-03-16Henrik Grubbström (Grubba) //! Return the set of properties for @[path].
b64c122003-08-26Henrik Grubbström (Grubba) //!
c5dd9f2004-03-16Henrik Grubbström (Grubba) //! @returns //! Returns @tt{0@} (zero) if @[path] does not exist.
b64c122003-08-26Henrik Grubbström (Grubba) //!
aecff12004-03-23Martin Stjernholm //! Returns an error mapping if there's some other error accessing //! the properties. //!
c5dd9f2004-03-16Henrik Grubbström (Grubba) //! Otherwise returns a @[PropertySet] object.
ea5df52005-02-25Henrik Grubbström (Grubba) //! //! @seealso //! @[query_property()]
ac173b2004-05-10Martin Stjernholm PropertySet|mapping(string:mixed) query_property_set(string path, RequestID id)
265ca42003-06-02Henrik Grubbström (Grubba) {
ca28e82004-03-23Martin Stjernholm  SIMPLE_TRACE_ENTER (this, "Querying properties on %O", path);
c5dd9f2004-03-16Henrik Grubbström (Grubba)  Stat st = stat_file(path, id);
ca28e82004-03-23Martin Stjernholm  if (!st) { SIMPLE_TRACE_LEAVE ("No such file or dir"); return 0; }
2ece722004-05-06Henrik Grubbström (Grubba)  PropertySet res = DefaultPropertySet(path, query_location()+path, id, st);
ca28e82004-03-23Martin Stjernholm  SIMPLE_TRACE_LEAVE (""); return res;
265ca42003-06-02Henrik Grubbström (Grubba) } //! 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.
ea5df52005-02-25Henrik Grubbström (Grubba) //! //! @seealso //! @[query_property_set()]
5da76f2004-05-10Henrik Grubbström (Grubba) string|array(Parser.XML.Tree.SimpleNode)|mapping(string:mixed)
c5dd9f2004-03-16Henrik Grubbström (Grubba)  query_property(string path, string prop_name, RequestID id)
265ca42003-06-02Henrik Grubbström (Grubba) {
ac173b2004-05-10Martin Stjernholm  mapping(string:mixed)|PropertySet properties = query_property_set(path, id);
c5dd9f2004-03-16Henrik Grubbström (Grubba)  if (!properties) { return Roxen.http_status(Protocols.HTTP.HTTP_NOT_FOUND, "No such file or directory.");
265ca42003-06-02Henrik Grubbström (Grubba)  }
aecff12004-03-23Martin Stjernholm  if (mappingp (properties)) return properties;
c5dd9f2004-03-16Henrik Grubbström (Grubba)  return properties->query_property(prop_name) || Roxen.http_status(Protocols.HTTP.HTTP_NOT_FOUND, "No such property.");
265ca42003-06-02Henrik Grubbström (Grubba) }
762b932004-03-03Martin Stjernholm //! RFC 2518 PROPFIND implementation with recursion according to
ea5df52005-02-25Henrik Grubbström (Grubba) //! @[depth]. See @[PropertySet()->find_properties()] for details. //! //! @seealso //! @[query_property_set()]
e9ceb92004-06-02Martin Stjernholm mapping(string:mixed) recurse_find_properties(string path, string mode, int depth, RequestID id, multiset(string)|void filt)
265ca42003-06-02Henrik Grubbström (Grubba) {
65f9ae2012-01-23Henrik Grubbström (Grubba)  string prefix = map(query_location()[1..]/"/", Roxen.http_encode_url)*"/";
113fa12004-05-10Martin Stjernholm  MultiStatus.Prefixed result =
65f9ae2012-01-23Henrik Grubbström (Grubba)  id->get_multi_status()->prefix (id->url_base() + prefix);
113fa12004-05-10Martin Stjernholm 
e9ceb92004-06-02Martin Stjernholm  mapping(string:mixed) recurse (string path, int depth) {
113fa12004-05-10Martin Stjernholm  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");
e9ceb92004-06-02Martin Stjernholm  return 0;
762b932004-03-03Martin Stjernholm  }
ca28e82004-03-23Martin Stjernholm 
113fa12004-05-10Martin Stjernholm  { 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);
e9ceb92004-06-02Martin Stjernholm  return ret;
113fa12004-05-10Martin Stjernholm  }
ca28e82004-03-23Martin Stjernholm  }
113fa12004-05-10Martin Stjernholm  if (properties->get_stat()->isdir) { if (depth <= 0) { SIMPLE_TRACE_LEAVE ("Not recursing due to depth limit");
614a2a2010-03-31Martin Jonsson  return ([]);
113fa12004-05-10Martin Stjernholm  } depth--; foreach(find_dir(path, id) || ({}), string filename) {
e9ceb92004-06-02Martin Stjernholm  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);
113fa12004-05-10Martin Stjernholm  }
ca28e82004-03-23Martin Stjernholm  }
113fa12004-05-10Martin Stjernholm  SIMPLE_TRACE_LEAVE ("");
e9ceb92004-06-02Martin Stjernholm  return ([]);
113fa12004-05-10Martin Stjernholm  };
e9ceb92004-06-02Martin Stjernholm  return recurse (path, depth);
265ca42003-06-02Henrik Grubbström (Grubba) }
a98f752004-03-03Henrik Grubbström (Grubba) mapping(string:mixed) patch_properties(string path, array(PatchPropertyCommand) instructions,
113fa12004-05-10Martin Stjernholm  RequestID id)
265ca42003-06-02Henrik Grubbström (Grubba) {
ca28e82004-03-23Martin Stjernholm  SIMPLE_TRACE_ENTER (this, "Patching properties for %O", path);
ac173b2004-05-10Martin Stjernholm  mapping(string:mixed)|PropertySet properties = query_property_set(path, id);
265ca42003-06-02Henrik Grubbström (Grubba) 
ca28e82004-03-23Martin Stjernholm  if (!properties) { SIMPLE_TRACE_LEAVE ("No such file or dir"); return 0; } if (mappingp (properties)) {
ac173b2004-05-10Martin Stjernholm  SIMPLE_TRACE_LEAVE ("Got error %d from query_property_set: %O",
ca28e82004-03-23Martin Stjernholm  properties->error, properties->rettext); return properties; }
a98f752004-03-03Henrik Grubbström (Grubba) 
0dbbfd2004-05-10Henrik Grubbström (Grubba)  mapping(string:mixed) errcode;
0cb4c42004-03-15Martin Stjernholm 
0dbbfd2004-05-10Henrik Grubbström (Grubba)  if (errcode = write_access(path, 0, id)) { SIMPLE_TRACE_LEAVE("Patching denied by write_access()."); return errcode; } if (errcode = properties->start()) {
ca28e82004-03-23Martin Stjernholm  SIMPLE_TRACE_LEAVE ("Got error %d from PropertySet.start: %O", errcode->error, errcode->rettext); return errcode; }
0cb4c42004-03-15Martin Stjernholm 
c5dd9f2004-03-16Henrik Grubbström (Grubba)  array(mapping(string:mixed)) results; mixed err = catch {
df04242004-03-16Henrik Grubbström (Grubba)  results = instructions->execute(properties);
c5dd9f2004-03-16Henrik Grubbström (Grubba)  }; if (err) { properties->unroll(); throw (err); } else {
65f9ae2012-01-23Henrik Grubbström (Grubba)  string prefix = map((query_location()[1..] + path)/"/", Roxen.http_encode_url)*"/";
113fa12004-05-10Martin Stjernholm  MultiStatus.Prefixed result =
65f9ae2012-01-23Henrik Grubbström (Grubba)  id->get_multi_status()->prefix (id->url_base() + prefix);
c5dd9f2004-03-16Henrik Grubbström (Grubba)  int any_failed; foreach(results, mapping(string:mixed) answer) { if (any_failed = (answer && (answer->error >= 300))) { break;
265ca42003-06-02Henrik Grubbström (Grubba)  }
0cb4c42004-03-15Martin Stjernholm  }
c5dd9f2004-03-16Henrik Grubbström (Grubba)  if (any_failed) { // Unroll and fail any succeeded items. int i; mapping(string:mixed) answer =
0c5a852004-05-12Martin Stjernholm  Roxen.http_status (Protocols.HTTP.DAV_FAILED_DEP);
c5dd9f2004-03-16Henrik Grubbström (Grubba)  for(i = 0; i < sizeof(results); i++) { if (!results[i] || results[i]->error < 300) {
113fa12004-05-10Martin Stjernholm  result->add_property("", instructions[i]->property_name,
c5dd9f2004-03-16Henrik Grubbström (Grubba)  answer); } else {
113fa12004-05-10Martin Stjernholm  result->add_property("", instructions[i]->property_name,
c5dd9f2004-03-16Henrik Grubbström (Grubba)  results[i]); } } properties->unroll(); } else {
265ca42003-06-02Henrik Grubbström (Grubba)  int i; for(i = 0; i < sizeof(results); i++) {
113fa12004-05-10Martin Stjernholm  result->add_property("", instructions[i]->property_name,
265ca42003-06-02Henrik Grubbström (Grubba)  results[i]); }
c5dd9f2004-03-16Henrik Grubbström (Grubba)  properties->commit();
265ca42003-06-02Henrik Grubbström (Grubba)  } }
0cb4c42004-03-15Martin Stjernholm 
ca28e82004-03-23Martin Stjernholm  SIMPLE_TRACE_LEAVE ("");
a98f752004-03-03Henrik Grubbström (Grubba)  return 0;
265ca42003-06-02Henrik Grubbström (Grubba) }
c5dd9f2004-03-16Henrik Grubbström (Grubba) //! Convenience variant of @[patch_properties()] that sets a single
aecff12004-03-23Martin Stjernholm //! property.
0cb4c42004-03-15Martin Stjernholm //! //! @returns //! Returns a mapping on any error, zero otherwise.
aecff12004-03-23Martin Stjernholm mapping(string:mixed) set_property (string path, string prop_name,
5da76f2004-05-10Henrik Grubbström (Grubba)  string|array(Parser.XML.Tree.SimpleNode) value,
aecff12004-03-23Martin Stjernholm  RequestID id)
25ceaf2004-03-01Martin Stjernholm {
ac173b2004-05-10Martin Stjernholm  mapping(string:mixed)|PropertySet properties = query_property_set(path, id);
c5dd9f2004-03-16Henrik Grubbström (Grubba)  if (!properties) return Roxen.http_status(Protocols.HTTP.HTTP_NOT_FOUND, "File not found.");
aecff12004-03-23Martin Stjernholm  if (mappingp (properties)) return properties;
0cb4c42004-03-15Martin Stjernholm 
c5dd9f2004-03-16Henrik Grubbström (Grubba)  mapping(string:mixed) result = properties->start(); if (result) return result; result = properties->set_property(prop_name, value);
aecff12004-03-23Martin Stjernholm  if (result && result->error >= 300) {
c5dd9f2004-03-16Henrik Grubbström (Grubba)  properties->unroll();
aecff12004-03-23Martin Stjernholm  return result; } properties->commit(); return 0;
25ceaf2004-03-01Martin Stjernholm }
c5dd9f2004-03-16Henrik Grubbström (Grubba) //! Convenience variant of @[patch_properties()] that removes a single
0cb4c42004-03-15Martin Stjernholm //! property. //! //! @returns //! Returns a mapping on any error, zero otherwise.
aecff12004-03-23Martin Stjernholm mapping(string:mixed) remove_property (string path, string prop_name, RequestID id)
25ceaf2004-03-01Martin Stjernholm {
ac173b2004-05-10Martin Stjernholm  mapping(string:mixed)|PropertySet properties = query_property_set(path, id);
c5dd9f2004-03-16Henrik Grubbström (Grubba)  if (!properties) return Roxen.http_status(Protocols.HTTP.HTTP_NOT_FOUND, "File not found.");
aecff12004-03-23Martin Stjernholm  if (mappingp (properties)) return properties;
c5dd9f2004-03-16Henrik Grubbström (Grubba)  mapping(string:mixed) result = properties->start(); if (result) return result;
0cb4c42004-03-15Martin Stjernholm 
c5dd9f2004-03-16Henrik Grubbström (Grubba)  result = properties->remove_property(prop_name);
aecff12004-03-23Martin Stjernholm  if (result && result->error >= 300) {
c5dd9f2004-03-16Henrik Grubbström (Grubba)  properties->unroll();
aecff12004-03-23Martin Stjernholm  return result; } properties->commit(); return 0;
25ceaf2004-03-01Martin Stjernholm }
8649802004-05-15Henrik Grubbström (Grubba) string resource_id (string path, RequestID|int(0..0) id)
0ddcae2004-05-04Martin Stjernholm //! 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.
12cfc52004-04-28Henrik Grubbström (Grubba) //!
0ddcae2004-05-04Martin Stjernholm //! 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.
4908322004-04-29Martin Stjernholm //!
0ddcae2004-05-04Martin Stjernholm //! 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
de070f2004-05-04Martin Stjernholm //! The requested path below the filesystem location. It has been
0ddcae2004-05-04Martin Stjernholm //! normalized with @[VFS.normalize_path].
8649802004-05-15Henrik Grubbström (Grubba) //! //! @param id //! The request id may have the value @expr{0@} (zero) if called //! by @[Configuration()->expire_locks()].
0ddcae2004-05-04Martin Stjernholm { return has_suffix (path, "/") ? path : path + "/"; }
12cfc52004-04-28Henrik Grubbström (Grubba) 
de070f2004-05-04Martin Stjernholm string|int authenticated_user_id (string path, RequestID id)
0ddcae2004-05-04Martin Stjernholm //! Return a value that uniquely identifies the user that the given //! request is authenticated as.
12cfc52004-04-28Henrik Grubbström (Grubba) //!
0ddcae2004-05-04Martin Stjernholm //! This function is e.g. used by the default lock implementation to
aa30fc2004-05-14Martin Stjernholm //! 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.
4908322004-04-29Martin Stjernholm //!
de070f2004-05-04Martin Stjernholm //! @param path //! The requested path below the filesystem location. It has been //! normalized with @[VFS.normalize_path].
0ddcae2004-05-04Martin Stjernholm { // 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.
fc40392008-08-15Martin Stjernholm protected mapping(string:mapping(mixed:DAVLock)) file_locks = ([]);
0ddcae2004-05-04Martin Stjernholm  // 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.
fc40392008-08-15Martin Stjernholm protected mapping(string:mapping(mixed:DAVLock)) prefix_locks = ([]);
0ddcae2004-05-04Martin Stjernholm  #define LOOP_OVER_BOTH(PATH, LOCKS, CODE) \ do { \ foreach (file_locks; PATH; LOCKS) {CODE;} \ foreach (prefix_locks; PATH; LOCKS) {CODE;} \ } while (0)
12cfc52004-04-28Henrik Grubbström (Grubba) 
47fa212004-05-03Martin Stjernholm //! Find some or all locks that apply to @[path].
12cfc52004-04-28Henrik Grubbström (Grubba) //! //! @param path
c6ce732004-05-07Martin Stjernholm //! Normalized path below the filesystem location.
12cfc52004-04-28Henrik Grubbström (Grubba) //! //! @param recursive
0ddcae2004-05-04Martin Stjernholm //! If @expr{1@} also return locks anywhere below @[path].
2656742018-02-01Henrik Grubbström (Grubba) //! 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.)
12cfc52004-04-28Henrik Grubbström (Grubba) //!
47fa212004-05-03Martin Stjernholm //! @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.) //!
12cfc52004-04-28Henrik Grubbström (Grubba) //! @returns //! Returns a multiset containing all applicable locks in //! this location module, or @expr{0@} (zero) if there are none.
4908322004-04-29Martin Stjernholm //! //! @note //! @[DAVLock] objects may be created if the filesystem has some
47fa212004-05-03Martin Stjernholm //! persistent storage of them. The default implementation does not //! store locks persistently.
4908322004-04-29Martin Stjernholm //! //! @note //! The default implementation only handles the @expr{"DAV:write"@} //! lock type.
47fa212004-05-03Martin Stjernholm multiset(DAVLock) find_locks(string path,
2656742018-02-01Henrik Grubbström (Grubba)  int(-1..1) recursive,
47fa212004-05-03Martin Stjernholm  int(0..1) exclude_shared, RequestID id)
12cfc52004-04-28Henrik Grubbström (Grubba) { // Common case. if (!sizeof(file_locks) && !sizeof(prefix_locks)) return 0;
090cdd2004-05-04Henrik Grubbström (Grubba)  TRACE_ENTER(sprintf("find_locks(%O, %O, %O, X)", path, recursive, exclude_shared), this);
87c7722004-05-14Martin Stjernholm  string rsc = resource_id (path, id);
0ddcae2004-05-04Martin Stjernholm 
12cfc52004-04-28Henrik Grubbström (Grubba)  multiset(DAVLock) locks = (<>);
0ddcae2004-05-04Martin Stjernholm  function(mapping(mixed:DAVLock):void) add_locks;
47fa212004-05-03Martin Stjernholm  if (exclude_shared) {
de070f2004-05-04Martin Stjernholm  mixed auth_user = authenticated_user_id (path, id);
0ddcae2004-05-04Martin Stjernholm  add_locks = lambda (mapping(mixed:DAVLock) sub_locks) {
47fa212004-05-03Martin Stjernholm  foreach (sub_locks; string user; DAVLock lock)
090cdd2004-05-04Henrik Grubbström (Grubba)  if (user == auth_user || lock->lockscope == "DAV:exclusive")
47fa212004-05-03Martin Stjernholm  locks[lock] = 1; }; } else
0ddcae2004-05-04Martin Stjernholm  add_locks = lambda (mapping(mixed:DAVLock) sub_locks) {
47fa212004-05-03Martin Stjernholm  locks |= mkmultiset (values (sub_locks)); };
87c7722004-05-14Martin Stjernholm  if (file_locks[rsc]) { add_locks (file_locks[rsc]);
12cfc52004-04-28Henrik Grubbström (Grubba)  }
47fa212004-05-03Martin Stjernholm 
2656742018-02-01Henrik Grubbström (Grubba)  if (recursive >= 0) { foreach(prefix_locks; string prefix; mapping(mixed:DAVLock) sub_locks) { if (has_prefix(rsc, prefix)) { add_locks (sub_locks); break; }
12cfc52004-04-28Henrik Grubbström (Grubba)  } }
47fa212004-05-03Martin Stjernholm 
12cfc52004-04-28Henrik Grubbström (Grubba)  if (recursive) {
0ddcae2004-05-04Martin Stjernholm  LOOP_OVER_BOTH (string prefix, mapping(mixed:DAVLock) sub_locks, {
87c7722004-05-14Martin Stjernholm  if (has_prefix(prefix, rsc)) {
0ddcae2004-05-04Martin Stjernholm  add_locks (sub_locks); } });
12cfc52004-04-28Henrik Grubbström (Grubba)  }
47fa212004-05-03Martin Stjernholm 
a1f0112004-05-04Henrik Grubbström (Grubba)  add_locks = 0;
090cdd2004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE(sprintf("Done, found %d locks.", sizeof(locks)));
12cfc52004-04-28Henrik Grubbström (Grubba)  return sizeof(locks) && locks; }
4908322004-04-29Martin Stjernholm //! Check if there are one or more locks that apply to @[path] for the //! user the request is authenticated as.
12cfc52004-04-28Henrik Grubbström (Grubba) //!
aa30fc2004-05-14Martin Stjernholm //! WARNING: This function has some design issues and will very likely //! get a different interface. Compatibility is NOT guaranteed. //!
12cfc52004-04-28Henrik Grubbström (Grubba) //! @param path
c6ce732004-05-07Martin Stjernholm //! Normalized path below the filesystem location.
12cfc52004-04-28Henrik Grubbström (Grubba) //! //! @param recursive
4908322004-04-29Martin Stjernholm //! If @expr{1@} also check recursively under @[path] for locks.
12cfc52004-04-28Henrik Grubbström (Grubba) //! //! @returns
c6ce732004-05-07Martin Stjernholm //! The valid return values are:
12cfc52004-04-28Henrik Grubbström (Grubba) //! @mixed //! @type DAVLock
c6ce732004-05-07Martin Stjernholm //! 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
12cfc52004-04-28Henrik Grubbström (Grubba) //! @int
dd03b22006-04-20Henrik Grubbström (Grubba) //! @value LOCK_NONE //! No locks apply. (0) //! @value LOCK_SHARED_BELOW
c6ce732004-05-07Martin Stjernholm //! There are only one or more shared locks held by other //! users somewhere below @[path] (but not on @[path]
dd03b22006-04-20Henrik Grubbström (Grubba) //! itself). Only returned if @[recursive] is set. (2) //! @value LOCK_SHARED_AT
c6ce732004-05-07Martin Stjernholm //! There are only one or more shared locks held by other
dd03b22006-04-20Henrik Grubbström (Grubba) //! users on @[path]. (3) //! @value LOCK_OWN_BELOW
c6ce732004-05-07Martin Stjernholm //! The authenticated user has locks under @[path] (but not //! on @[path] itself) and there are no exclusive locks held
dd03b22006-04-20Henrik Grubbström (Grubba) //! by other users. Only returned if @[recursive] is set. (4) //! @value LOCK_EXCL_BELOW
c6ce732004-05-07Martin Stjernholm //! There are one or more exclusive locks held by other //! users somewhere below @[path] (but not on @[path]
dd03b22006-04-20Henrik Grubbström (Grubba) //! itself). Only returned if @[recursive] is set. (6) //! @value LOCK_EXCL_AT
c6ce732004-05-07Martin Stjernholm //! There are one or more exclusive locks held by other
dd03b22006-04-20Henrik Grubbström (Grubba) //! users on @[path]. (7)
12cfc52004-04-28Henrik Grubbström (Grubba) //! @endint
c6ce732004-05-07Martin Stjernholm //! Note that the lowest bit is set for all flags that apply to //! @[path] itself.
12cfc52004-04-28Henrik Grubbström (Grubba) //! @endmixed
4908322004-04-29Martin Stjernholm //! //! @note //! @[DAVLock] objects may be created if the filesystem has some
47fa212004-05-03Martin Stjernholm //! persistent storage of them. The default implementation does not //! store locks persistently.
4908322004-04-29Martin Stjernholm //! //! @note //! The default implementation only handles the @expr{"DAV:write"@} //! lock type.
c6ce732004-05-07Martin Stjernholm DAVLock|LockFlag check_locks(string path, int(0..1) recursive, RequestID id)
12cfc52004-04-28Henrik Grubbström (Grubba) {
090cdd2004-05-04Henrik Grubbström (Grubba)  TRACE_ENTER(sprintf("check_locks(%O, %d, X)", path, recursive), this);
aa30fc2004-05-14Martin Stjernholm  // Common case. if (!sizeof(file_locks) && !sizeof(prefix_locks)) { TRACE_LEAVE ("Got no locks"); return 0; }
de070f2004-05-04Martin Stjernholm  mixed auth_user = authenticated_user_id (path, id);
87c7722004-05-14Martin Stjernholm  path = resource_id (path, id);
4908322004-04-29Martin Stjernholm  if (DAVLock lock = file_locks[path] && file_locks[path][auth_user] ||
090cdd2004-05-04Henrik Grubbström (Grubba)  prefix_locks[path] && prefix_locks[path][auth_user]) {
abefea2004-05-17Martin Stjernholm  TRACE_LEAVE(sprintf("Found own lock %O.", lock->locktoken));
4908322004-04-29Martin Stjernholm  return lock;
090cdd2004-05-04Henrik Grubbström (Grubba)  }
c6ce732004-05-07Martin Stjernholm  LockFlag shared;
4908322004-04-29Martin Stjernholm 
0ddcae2004-05-04Martin Stjernholm  if (mapping(mixed:DAVLock) locks = file_locks[path]) {
4908322004-04-29Martin Stjernholm  foreach(locks;; DAVLock lock) {
090cdd2004-05-04Henrik Grubbström (Grubba)  if (lock->lockscope == "DAV:exclusive") { TRACE_LEAVE(sprintf("Found other user's exclusive lock %O.", lock->locktoken));
c6ce732004-05-07Martin Stjernholm  return LOCK_EXCL_AT;
090cdd2004-05-04Henrik Grubbström (Grubba)  }
c6ce732004-05-07Martin Stjernholm  shared = LOCK_SHARED_AT;
12cfc52004-04-28Henrik Grubbström (Grubba)  break; } }
4908322004-04-29Martin Stjernholm  foreach(prefix_locks;
0ddcae2004-05-04Martin Stjernholm  string prefix; mapping(mixed:DAVLock) locks) {
12cfc52004-04-28Henrik Grubbström (Grubba)  if (has_prefix(path, prefix)) {
abefea2004-05-17Martin Stjernholm  if (DAVLock lock = locks[auth_user]) { SIMPLE_TRACE_LEAVE ("Found own lock %O on %O.", lock->locktoken, prefix); return lock; }
4908322004-04-29Martin Stjernholm  if (!shared) // If we've found a shared lock then we won't find an
0ddcae2004-05-04Martin Stjernholm  // exclusive one anywhere else.
4908322004-04-29Martin Stjernholm  foreach(locks;; DAVLock lock) {
090cdd2004-05-04Henrik Grubbström (Grubba)  if (lock->lockscope == "DAV:exclusive") { TRACE_LEAVE(sprintf("Found other user's exclusive lock %O.", lock->locktoken));
c6ce732004-05-07Martin Stjernholm  return LOCK_EXCL_AT;
090cdd2004-05-04Henrik Grubbström (Grubba)  }
c6ce732004-05-07Martin Stjernholm  shared = LOCK_SHARED_AT;
4908322004-04-29Martin Stjernholm  break; }
12cfc52004-04-28Henrik Grubbström (Grubba)  } }
090cdd2004-05-04Henrik Grubbström (Grubba)  if (!recursive) {
aa30fc2004-05-14Martin Stjernholm  SIMPLE_TRACE_LEAVE("Returning %O.", shared);
090cdd2004-05-04Henrik Grubbström (Grubba)  return shared; }
12cfc52004-04-28Henrik Grubbström (Grubba) 
4908322004-04-29Martin Stjernholm  int(0..1) locked_by_auth_user;
12cfc52004-04-28Henrik Grubbström (Grubba)  // We want to know if there are any locks with @[path] as prefix // that apply to us.
0ddcae2004-05-04Martin Stjernholm  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));
c6ce732004-05-07Martin Stjernholm  return LOCK_EXCL_BELOW;
0ddcae2004-05-04Martin Stjernholm  }
c6ce732004-05-07Martin Stjernholm  if (!shared) shared = LOCK_SHARED_BELOW;
0ddcae2004-05-04Martin Stjernholm  break;
090cdd2004-05-04Henrik Grubbström (Grubba)  }
0ddcae2004-05-04Martin Stjernholm  } });
4908322004-04-29Martin Stjernholm 
aa30fc2004-05-14Martin Stjernholm  SIMPLE_TRACE_LEAVE("Returning %O.", locked_by_auth_user ? LOCK_OWN_BELOW : shared);
c6ce732004-05-07Martin Stjernholm  return locked_by_auth_user ? LOCK_OWN_BELOW : shared;
12cfc52004-04-28Henrik Grubbström (Grubba) }
51c8d12004-05-05Martin Stjernholm //! Register @[lock] on the path @[path] under the assumption that //! there is no other lock already that conflicts with this one, i.e.
dd03b22006-04-20Henrik Grubbström (Grubba) //! that @expr{check_locks(path,lock->recursive,id)@} would return
c6ce732004-05-07Martin Stjernholm //! @expr{LOCK_NONE@} if @expr{lock->lockscope@} is //! @expr{"DAV:exclusive"@}, or @expr{< LOCK_OWN_BELOW@} if //! @expr{lock->lockscope@} is @expr{"DAV:shared"@}.
12cfc52004-04-28Henrik Grubbström (Grubba) //!
51c8d12004-05-05Martin Stjernholm //! This function is only provided as a helper to call from //! @[lock_file] if the default lock implementation is to be used.
12cfc52004-04-28Henrik Grubbström (Grubba) //! //! @param path
0c5a852004-05-12Martin Stjernholm //! Normalized path (below the filesystem location) that the lock
c6ce732004-05-07Martin Stjernholm //! applies to.
12cfc52004-04-28Henrik Grubbström (Grubba) //! //! @param lock //! The lock to register. //!
4908322004-04-29Martin Stjernholm //! @note
51c8d12004-05-05Martin Stjernholm //! 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.
fc40392008-08-15Martin Stjernholm protected void register_lock(string path, DAVLock lock, RequestID id)
12cfc52004-04-28Henrik Grubbström (Grubba) {
51c8d12004-05-05Martin Stjernholm  TRACE_ENTER(sprintf("register_lock(%O, lock(%O), X).", path, lock->locktoken),
090cdd2004-05-04Henrik Grubbström (Grubba)  this);
51c8d12004-05-05Martin Stjernholm  ASSERT_IF_DEBUG (lock->locktype == "DAV:write");
de070f2004-05-04Martin Stjernholm  mixed auth_user = authenticated_user_id (path, id);
87c7722004-05-14Martin Stjernholm  path = resource_id (path, id);
12cfc52004-04-28Henrik Grubbström (Grubba)  if (lock->recursive) { if (prefix_locks[path]) {
0ddcae2004-05-04Martin Stjernholm  prefix_locks[path][auth_user] = lock;
12cfc52004-04-28Henrik Grubbström (Grubba)  } else {
0ddcae2004-05-04Martin Stjernholm  prefix_locks[path] = ([ auth_user:lock ]);
12cfc52004-04-28Henrik Grubbström (Grubba)  } } else { if (file_locks[path]) {
0ddcae2004-05-04Martin Stjernholm  file_locks[path][auth_user] = lock;
12cfc52004-04-28Henrik Grubbström (Grubba)  } else {
0ddcae2004-05-04Martin Stjernholm  file_locks[path] = ([ auth_user:lock ]);
12cfc52004-04-28Henrik Grubbström (Grubba)  } }
090cdd2004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("Ok.");
51c8d12004-05-05Martin Stjernholm }
aa30fc2004-05-14Martin Stjernholm //! 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.) //!
8649802004-05-15Henrik Grubbström (Grubba) //! @param id //! The request id may have the value @expr{0@} (zero) if called //! by @[Configuration()->expire_locks()].
fc40392008-08-15Martin Stjernholm protected void unregister_lock (string path, DAVLock lock, RequestID|int(0..0) id)
aa30fc2004-05-14Martin Stjernholm { TRACE_ENTER(sprintf("unregister_lock(%O, lock(%O), X).", path, lock->locktoken), this);
8649802004-05-15Henrik Grubbström (Grubba)  mixed auth_user = id && authenticated_user_id (path, id);
aa30fc2004-05-14Martin Stjernholm  path = resource_id (path, id); DAVLock removed_lock; if (lock->recursive) {
8649802004-05-15Henrik Grubbström (Grubba)  if (id) { removed_lock = m_delete(prefix_locks[path], auth_user); } else {
d057442018-02-06Henrik Grubbström (Grubba)  foreach(prefix_locks[path]||([]); mixed user; DAVLock l) {
8649802004-05-15Henrik Grubbström (Grubba)  if (l == lock) { removed_lock = m_delete(prefix_locks[path], user); } } }
aa30fc2004-05-14Martin Stjernholm  if (!sizeof (prefix_locks[path])) m_delete (prefix_locks, path); } else if (file_locks[path]) {
8649802004-05-15Henrik Grubbström (Grubba)  if (id) { removed_lock = m_delete (file_locks[path], auth_user); } else {
d057442018-02-06Henrik Grubbström (Grubba)  foreach(file_locks[path]||([]); mixed user; DAVLock l) {
8649802004-05-15Henrik Grubbström (Grubba)  if (l == lock) { removed_lock = m_delete(file_locks[path], user); } } }
aa30fc2004-05-14Martin Stjernholm  if (!sizeof (file_locks[path])) m_delete (file_locks, path); }
d057442018-02-06Henrik Grubbström (Grubba)  // 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);
aa30fc2004-05-14Martin Stjernholm  TRACE_LEAVE("Ok."); return 0; }
51c8d12004-05-05Martin Stjernholm //! Register @[lock] on the path @[path] under the assumption that //! there is no other lock already that conflicts with this one, i.e.
dd03b22006-04-20Henrik Grubbström (Grubba) //! that @expr{check_locks(path,lock->recursive,id)@} would return
c6ce732004-05-07Martin Stjernholm //! @expr{LOCK_NONE@} if @expr{lock->lockscope@} is //! @expr{"DAV:exclusive"@}, or @expr{<= LOCK_SHARED_AT@} if //! @expr{lock->lockscope@} is @expr{"DAV:shared"@}.
51c8d12004-05-05Martin Stjernholm //! //! 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
c6ce732004-05-07Martin Stjernholm //! 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
aa30fc2004-05-14Martin Stjernholm //! RFC 2518Bis (working draft)). The default implementation fulfills //! these criteria.
51c8d12004-05-05Martin Stjernholm //! //! 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
0c5a852004-05-12Martin Stjernholm //! Normalized path (below the filesystem location) that the lock
c6ce732004-05-07Martin Stjernholm //! applies to.
51c8d12004-05-05Martin Stjernholm //! //! @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.
aa30fc2004-05-14Martin Stjernholm //! //! @note //! To use the default lock implementation, call @[register_lock] //! from this function.
51c8d12004-05-05Martin Stjernholm mapping(string:mixed) lock_file(string path, DAVLock lock, RequestID id) {
12cfc52004-04-28Henrik Grubbström (Grubba)  return 0; }
4908322004-04-29Martin Stjernholm  //! Remove @[lock] that currently is locking the resource at @[path].
aa30fc2004-05-14Martin Stjernholm //! It's assumed that the lock is registered for exactly that path.
4908322004-04-29Martin Stjernholm //! //! @param path
0c5a852004-05-12Martin Stjernholm //! Normalized path (below the filesystem location) that the lock
c6ce732004-05-07Martin Stjernholm //! applies to.
4908322004-04-29Martin Stjernholm //! //! @param lock //! The lock to unregister. (It must not be changed or destructed.) //!
aa30fc2004-05-14Martin Stjernholm //! @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 //!
4908322004-04-29Martin Stjernholm //! @returns //! Returns a status mapping on any error, zero otherwise.
aa30fc2004-05-14Martin Stjernholm //! //! @note //! To use the default lock implementation, call @[unregister_lock] //! from this function.
4908322004-04-29Martin Stjernholm mapping(string:mixed) unlock_file (string path, DAVLock lock,
aa30fc2004-05-14Martin Stjernholm  RequestID|int(0..0) id);
4908322004-04-29Martin Stjernholm 
28c81a2004-05-12Martin Stjernholm //! 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].
daf03e2004-05-05Henrik Grubbström (Grubba) //!
aa30fc2004-05-14Martin Stjernholm //! WARNING: This function has some design issues and will very likely //! get a different interface. Compatibility is NOT guaranteed. //!
51c8d12004-05-05Martin Stjernholm //! @param path
0c5a852004-05-12Martin Stjernholm //! Path (below the filesystem location) that the lock applies to.
51c8d12004-05-05Martin Stjernholm //! //! @param recursive //! If @expr{1@} also check write access recursively under @[path]. //!
daf03e2004-05-05Henrik Grubbström (Grubba) //! @returns
c6ce732004-05-07Martin Stjernholm //! Returns @expr{0@} (zero) on success, a status mapping on //! failure, or @expr{1@} if @[recursive] is set and write access is
93686e2004-05-07Martin Stjernholm //! 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.
28c81a2004-05-12Martin Stjernholm mapping(string:mixed)|int(0..1) check_if_header(string relative_path, int(0..1) recursive, RequestID id)
daf03e2004-05-05Henrik Grubbström (Grubba) {
28c81a2004-05-12Martin Stjernholm  SIMPLE_TRACE_ENTER(this, "Checking \"If\" header for %O", relative_path);
1642dc2004-05-07Henrik Grubbström (Grubba) 
d914dd2004-05-10Henrik Grubbström (Grubba)  int/*LockFlag*/|DAVLock lock = check_locks(relative_path, recursive, id);
daf03e2004-05-05Henrik Grubbström (Grubba) 
c6ce732004-05-07Martin Stjernholm  int(0..1) got_sublocks;
93686e2004-05-07Martin Stjernholm  if (lock && intp(lock)) {
c6ce732004-05-07Martin Stjernholm  if (lock & 1) { TRACE_LEAVE("Locked by other user."); return Roxen.http_status(Protocols.HTTP.DAV_LOCKED); }
93686e2004-05-07Martin Stjernholm  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.
c6ce732004-05-07Martin Stjernholm  got_sublocks = 1;
1642dc2004-05-07Henrik Grubbström (Grubba)  }
51c8d12004-05-05Martin Stjernholm 
d914dd2004-05-10Henrik Grubbström (Grubba)  string path = relative_path;
c6ce732004-05-07Martin Stjernholm  if (!has_suffix (path, "/")) path += "/"; // get_if_data always adds a "/".
51c8d12004-05-05Martin Stjernholm  path = query_location() + path; // No need for fancy combine_path stuff here.
daf03e2004-05-05Henrik Grubbström (Grubba)  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])) {
1642dc2004-05-07Henrik Grubbström (Grubba)  if (lock) { TRACE_LEAVE("Locked, no if header."); return Roxen.http_status(Protocols.HTTP.DAV_LOCKED); }
93686e2004-05-07Martin Stjernholm  SIMPLE_TRACE_LEAVE("No lock and no if header - ok%s.", got_sublocks ? " (this level only)" : "");
c6ce732004-05-07Martin Stjernholm  return got_sublocks; // No condition and no lock -- Ok.
daf03e2004-05-05Henrik Grubbström (Grubba)  }
c6ce732004-05-07Martin Stjernholm 
d914dd2004-05-10Henrik Grubbström (Grubba)  string|int(-1..0) etag;
09006d2004-05-17Martin Stjernholm  int(0..1) locked_fail = !!lock;
daf03e2004-05-05Henrik Grubbström (Grubba)  next_condition: foreach(condition, array(array(string)) sub_cond) {
1642dc2004-05-07Henrik Grubbström (Grubba)  SIMPLE_TRACE_ENTER(this,
27e1b42004-05-07Henrik Grubbström (Grubba)  "Trying condition ( %{%s:%O %})...", sub_cond);
daf03e2004-05-05Henrik Grubbström (Grubba)  int negate;
27e1b42004-05-07Henrik Grubbström (Grubba)  DAVLock locked = lock;
daf03e2004-05-05Henrik Grubbström (Grubba)  foreach(sub_cond, array(string) token) { switch(token[0]) { case "not": negate = !negate; break; case "etag":
d914dd2004-05-10Henrik Grubbström (Grubba)  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;
27e1b42004-05-07Henrik Grubbström (Grubba)  case "key":
e66d2a2004-05-10Henrik Grubbström (Grubba)  // The user has specified a key, so don't fail with DAV_LOCKED.
09006d2004-05-17Martin Stjernholm  locked_fail = 0;
27e1b42004-05-07Henrik Grubbström (Grubba)  if (negate) { if (lock && lock->locktoken == token[1]) { TRACE_LEAVE("Matched negated lock."); continue next_condition; // Fail. } } else if (!lock || lock->locktoken != token[1]) {
daf03e2004-05-05Henrik Grubbström (Grubba)  // Lock mismatch.
1642dc2004-05-07Henrik Grubbström (Grubba)  TRACE_LEAVE("Lock mismatch.");
8668a82004-05-05Henrik Grubbström (Grubba)  continue next_condition; // Fail.
27e1b42004-05-07Henrik Grubbström (Grubba)  } else { locked = 0;
daf03e2004-05-05Henrik Grubbström (Grubba)  } negate = 0; break; } }
27e1b42004-05-07Henrik Grubbström (Grubba)  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);
09006d2004-05-17Martin Stjernholm  locked_fail = 1;
daf03e2004-05-05Henrik Grubbström (Grubba)  }
c6ce732004-05-07Martin Stjernholm 
d6df122018-02-01Henrik Grubbström (Grubba)  if (locked_fail) { TRACE_LEAVE("Failed (locked)."); } else { TRACE_LEAVE("Precondition failed."); }
09006d2004-05-17Martin Stjernholm  return Roxen.http_status(locked_fail ? Protocols.HTTP.DAV_LOCKED : Protocols.HTTP.HTTP_PRECOND_FAILED);
daf03e2004-05-05Henrik Grubbström (Grubba) }
12cfc52004-04-28Henrik Grubbström (Grubba) 
28c81a2004-05-12Martin Stjernholm //! 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. //!
aa30fc2004-05-14Martin Stjernholm //! WARNING: This function has some design issues and will very likely //! get a different interface. Compatibility is NOT guaranteed. //!
28c81a2004-05-12Martin Stjernholm //! A filesystem module should typically put all needed write access //! checks here and then use this from @[find_file()], //! @[delete_file()] etc.
97c6532018-02-16Henrik Grubbström (Grubba) //! //! @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.
fc40392008-08-15Martin Stjernholm protected mapping(string:mixed)|int(0..1) write_access(string relative_path, int(0..1) recursive, RequestID id)
28c81a2004-05-12Martin Stjernholm { return check_if_header (relative_path, recursive, id); }
97c6532018-02-16Henrik Grubbström (Grubba) //! 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; }
d396ad2004-03-03Henrik Grubbström (Grubba) mapping(string:mixed)|int(-1..0)|Stdio.File find_file(string path, RequestID id);
7406102004-05-13Martin Stjernholm //! Used by the default @[recurse_delete_files] implementation to //! delete a file or an empty directory.
08666d2004-05-12Martin Stjernholm //! //! @returns
7406102004-05-13Martin Stjernholm //! 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.
d396ad2004-03-03Henrik Grubbström (Grubba) //! //! @note //! The default implementation falls back to @[find_file()].
fc40392008-08-15Martin Stjernholm protected mapping(string:mixed) delete_file(string path, RequestID id)
d396ad2004-03-03Henrik Grubbström (Grubba) { // Fall back to find_file().
2ece722004-05-06Henrik Grubbström (Grubba)  RequestID tmp_id = id->clone_me();
d35dfd2004-04-20Martin Stjernholm  tmp_id->not_query = query_location() + path;
2ece722004-05-06Henrik Grubbström (Grubba)  tmp_id->method = "DELETE";
d396ad2004-03-03Henrik Grubbström (Grubba)  // FIXME: Logging?
d5c3192004-05-10Henrik Grubbström (Grubba)  return find_file(path, tmp_id) ||
0c5a852004-05-12Martin Stjernholm  tmp_id->misc->error_code && Roxen.http_status (tmp_id->misc->error_code);
d396ad2004-03-03Henrik Grubbström (Grubba) }
1642dc2004-05-07Henrik Grubbström (Grubba) //! Delete @[path] recursively.
0c5a852004-05-12Martin Stjernholm //! //! The default implementation handles the recursion and calls //! @[delete_file] for each file and empty directory. //!
1642dc2004-05-07Henrik Grubbström (Grubba) //! @returns
7406102004-05-13Martin Stjernholm //! 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()].
0dbbfd2004-05-10Henrik Grubbström (Grubba) mapping(string:mixed) recurse_delete_files(string path,
113fa12004-05-10Martin Stjernholm  RequestID id, void|MultiStatus.Prefixed stat)
d396ad2004-03-03Henrik Grubbström (Grubba) {
08666d2004-05-12Martin Stjernholm  SIMPLE_TRACE_ENTER (this, "Deleting %O recursively", path);
113fa12004-05-10Martin Stjernholm  if (!stat)
70f3852004-05-13Henrik Grubbström (Grubba)  stat = id->get_multi_status()->prefix (id->url_base() + query_location()[1..]);
113fa12004-05-10Martin Stjernholm 
08666d2004-05-12Martin Stjernholm  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)
113fa12004-05-10Martin Stjernholm  {
08666d2004-05-12Martin Stjernholm  // Note: Already got an extra TRACE_ENTER level on entry here.
113fa12004-05-10Martin Stjernholm  if (st->isdir) {
0dbbfd2004-05-10Henrik Grubbström (Grubba)  // RFC 2518 8.6.2
113fa12004-05-10Martin Stjernholm  // 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) {
08666d2004-05-12Martin Stjernholm  fname = path + fname; if (Stat sub_stat = stat_file (fname, id)) {
0c5a852004-05-12Martin Stjernholm  SIMPLE_TRACE_ENTER (this, "Deleting %O", fname);
d174872004-05-12Martin Stjernholm  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.
0c5a852004-05-12Martin Stjernholm  if (sizeof (sub_res) && sub_res->error != 204) {
d174872004-05-12Martin Stjernholm  stat->add_status(fname, sub_res->error, sub_res->rettext); }
4ba29a2004-05-13Martin Stjernholm  if (!sizeof (sub_res) || sub_res->error >= 300) fail = 1;
08666d2004-05-12Martin Stjernholm  }
113fa12004-05-10Martin Stjernholm  }
0dbbfd2004-05-10Henrik Grubbström (Grubba)  }
08666d2004-05-12Martin Stjernholm  if (fail) { SIMPLE_TRACE_LEAVE ("Partial failure");
0c5a852004-05-12Martin Stjernholm  return ([]);
08666d2004-05-12Martin Stjernholm  }
d396ad2004-03-03Henrik Grubbström (Grubba)  }
08666d2004-05-12Martin Stjernholm  SIMPLE_TRACE_LEAVE ("");
9001842004-05-12Martin Stjernholm  return delete_file (path, id);
113fa12004-05-10Martin Stjernholm  };
9001842004-05-12Martin Stjernholm  return recurse(path, st) || Roxen.http_status(204);
d396ad2004-03-03Henrik Grubbström (Grubba) }
7406102004-05-13Martin Stjernholm //! 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.
ac173b2004-05-10Martin Stjernholm mapping(string:mixed) make_collection(string path, RequestID id)
2351182004-05-08Henrik Grubbström (Grubba) { // Fall back to find_file(). RequestID tmp_id = id->clone_me(); tmp_id->not_query = query_location() + path; tmp_id->method = "MKCOL"; // FIXME: Logging?
689b412004-05-10Martin Stjernholm  return find_file(path, tmp_id);
2351182004-05-08Henrik Grubbström (Grubba) }
3452342004-05-13Martin Stjernholm //! Used by the default @[copy_collection] implementation to copy all //! properties at @[source] to @[destination].
7406102004-05-13Martin Stjernholm //! //! @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
aa30fc2004-05-14Martin Stjernholm //! @expr{0@} (zero) on success or an appropriate status mapping for
7406102004-05-13Martin Stjernholm //! any error.
fc40392008-08-15Martin Stjernholm protected mapping(string:mixed) copy_properties( string source, string destination, PropertyBehavior behavior, RequestID id)
5da76f2004-05-10Henrik Grubbström (Grubba) { SIMPLE_TRACE_ENTER(this, "copy_properties(%O, %O, %O, %O)", source, destination, behavior, id);
ac173b2004-05-10Martin Stjernholm  PropertySet source_properties = query_property_set(source, id); PropertySet destination_properties = query_property_set(destination, id);
5da76f2004-05-10Henrik Grubbström (Grubba)  multiset(string) property_set = source_properties->query_all_properties();
ac173b2004-05-10Martin Stjernholm  mapping(string:mixed) res;
5da76f2004-05-10Henrik Grubbström (Grubba)  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; }
ac173b2004-05-10Martin Stjernholm  mapping(string:mixed) subres =
5da76f2004-05-10Henrik Grubbström (Grubba)  destination_properties->set_property(property_name, source_val);
ba42f42004-05-13Henrik Grubbström (Grubba)  if (!behavior) {
5da76f2004-05-10Henrik Grubbström (Grubba)  TRACE_LEAVE("Omit verify."); continue; }
ba42f42004-05-13Henrik Grubbström (Grubba)  if ((intp(behavior) || behavior[property_name]) && (subres->error < 300)) {
5da76f2004-05-10Henrik Grubbström (Grubba)  // 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. }
ac173b2004-05-10Martin Stjernholm  if ((subres->error < 300) || (subres->error == Protocols.HTTP.HTTP_CONFLICT)) {
5da76f2004-05-10Henrik Grubbström (Grubba)  // Ok, or read-only property. TRACE_LEAVE("Copy ok or read-only property."); continue; }
ac173b2004-05-10Martin Stjernholm  if (!subres) {
5da76f2004-05-10Henrik Grubbström (Grubba)  // 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; }
4ba29a2004-05-13Martin Stjernholm //! 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()].
fc40392008-08-15Martin Stjernholm protected mapping(string:mixed) copy_collection( string source, string destination, PropertyBehavior behavior, Overwrite overwrite, MultiStatus.Prefixed result, RequestID id)
2351182004-05-08Henrik Grubbström (Grubba) {
5da76f2004-05-10Henrik Grubbström (Grubba)  SIMPLE_TRACE_ENTER(this, "copy_collection(%O, %O, %O, %O, %O, %O).", source, destination, behavior, overwrite, result, id);
2351182004-05-08Henrik Grubbström (Grubba)  Stat st = stat_file(destination, id); if (st) { // Destination exists. Check the overwrite header.
5da76f2004-05-10Henrik Grubbström (Grubba)  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);
113fa12004-05-10Martin Stjernholm  mapping(string:mixed) res = recurse_delete_files(destination, id, result);
e9ca9f2004-05-12Martin Stjernholm  if (res && (!sizeof (res) || res->error >= 300)) {
5da76f2004-05-10Henrik Grubbström (Grubba)  // Failed to delete something. TRACE_LEAVE("Deletion failed."); TRACE_LEAVE("Copy collection failed.");
e9ca9f2004-05-12Martin Stjernholm  // 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
2351182004-05-08Henrik Grubbström (Grubba)  return Roxen.http_status(Protocols.HTTP.HTTP_PRECOND_FAILED);
e9ca9f2004-05-12Martin Stjernholm #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.
7fe7b02004-05-12Martin Stjernholm  result->add_status (destination, res->error, res->rettext);
e9ca9f2004-05-12Martin Stjernholm  } return ([]); #endif
2351182004-05-08Henrik Grubbström (Grubba)  }
5da76f2004-05-10Henrik Grubbström (Grubba)  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:
2351182004-05-08Henrik Grubbström (Grubba)  // 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.");
5da76f2004-05-10Henrik Grubbström (Grubba)  return copy_properties(source, destination, behavior, id);
2351182004-05-08Henrik Grubbström (Grubba)  } 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.");
ac173b2004-05-10Martin Stjernholm  mapping(string:mixed) res = make_collection(destination, id);
5da76f2004-05-10Henrik Grubbström (Grubba)  if (res && res->error >= 300) return res; return copy_properties(source, destination, behavior, id) || res;
2351182004-05-08Henrik Grubbström (Grubba) }
7406102004-05-13Martin Stjernholm //! 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.
fc40392008-08-15Martin Stjernholm protected mapping(string:mixed) copy_file(string source, string destination, PropertyBehavior behavior, Overwrite overwrite, RequestID id)
265ca42003-06-02Henrik Grubbström (Grubba) {
5da76f2004-05-10Henrik Grubbström (Grubba)  SIMPLE_TRACE_ENTER(this, "copy_file(%O, %O, %O, %O, %O)\n",
113fa12004-05-10Martin Stjernholm  source, destination, behavior, overwrite, id);
0c5a852004-05-12Martin Stjernholm  TRACE_LEAVE("Not implemented.");
762b932004-03-03Martin Stjernholm  return Roxen.http_status (Protocols.HTTP.HTTP_NOT_IMPL);
6413232004-03-01Henrik Grubbström (Grubba) }
265ca42003-06-02Henrik Grubbström (Grubba) 
7406102004-05-13Martin Stjernholm //! 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,
ba42f42004-05-13Henrik Grubbström (Grubba)  PropertyBehavior behavior,
113fa12004-05-10Martin Stjernholm  Overwrite overwrite, RequestID id)
6413232004-03-01Henrik Grubbström (Grubba) {
7406102004-05-13Martin Stjernholm  SIMPLE_TRACE_ENTER(this, "Recursive copy from %O to %O (%s)", source, destination, overwrite == DO_OVERWRITE ? "replace" : overwrite == NEVER_OVERWRITE ? "no overwrite" : "overlay");
e66d2a2004-05-10Henrik Grubbström (Grubba)  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)) {
a9f4fc2004-05-10Henrik Grubbström (Grubba)  TRACE_LEAVE("Source and destination overlap."); return Roxen.http_status(403, "Source and destination overlap.");
2351182004-05-08Henrik Grubbström (Grubba)  }
113fa12004-05-10Martin Stjernholm 
65f9ae2012-01-23Henrik Grubbström (Grubba)  string prefix = map(query_location()[1..]/"/", Roxen.http_encode_url)*"/";
113fa12004-05-10Martin Stjernholm  MultiStatus.Prefixed result =
65f9ae2012-01-23Henrik Grubbström (Grubba)  id->get_multi_status()->prefix (id->url_base() + prefix);
113fa12004-05-10Martin Stjernholm 
7406102004-05-13Martin Stjernholm  mapping(string:mixed) recurse(string source, string destination) {
113fa12004-05-10Martin Stjernholm  // Note: Already got an extra TRACE_ENTER level on entry here. Stat st = stat_file(source, id); if (!st) { TRACE_LEAVE("Source not found.");
7406102004-05-13Martin Stjernholm  return 0;
113fa12004-05-10Martin Stjernholm  } // FIXME: Check destination? if (st->isdir) { mapping(string:mixed) res =
ba42f42004-05-13Henrik Grubbström (Grubba)  copy_collection(source, destination, behavior, overwrite, result, id);
4ba29a2004-05-13Martin Stjernholm  if (res && (!sizeof (res) || res->error >= 300)) {
0c5a852004-05-12Martin Stjernholm  // RFC 2518 8.8.3 and 8.8.8 (error minimization). TRACE_LEAVE("Copy of collection failed."); return res;
113fa12004-05-10Martin Stjernholm  } foreach(find_dir(source, id), string filename) { string subsrc = combine_path_unix(source, filename); string subdst = combine_path_unix(destination, filename);
7406102004-05-13Martin Stjernholm  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]) {
0c5a852004-05-12Martin Stjernholm  result->add_status(subdst, sub_res->error, sub_res->rettext);
113fa12004-05-10Martin Stjernholm  }
2351182004-05-08Henrik Grubbström (Grubba)  }
113fa12004-05-10Martin Stjernholm  TRACE_LEAVE(""); return res; } else { TRACE_LEAVE("");
ba42f42004-05-13Henrik Grubbström (Grubba)  return copy_file(source, destination, behavior, overwrite, id);
6413232004-03-01Henrik Grubbström (Grubba)  }
113fa12004-05-10Martin Stjernholm  };
0c5a852004-05-12Martin Stjernholm  int start_ms_size = id->multi_status_size();
7406102004-05-13Martin Stjernholm  mapping(string:mixed) res = recurse (source, destination);
0c5a852004-05-12Martin Stjernholm  if (res && res->error != 204 && res->error != 201) return res; else if (id->multi_status_size() != start_ms_size) return ([]); else return res;
265ca42003-06-02Henrik Grubbström (Grubba) }
7406102004-05-13Martin Stjernholm //! Used by the default @[recurse_move_files] to move a file (and not //! a directory) from @[source] to @[destination]. //!
4ba29a2004-05-13Martin Stjernholm //! 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. //!
7406102004-05-13Martin Stjernholm //! @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()].
fc40392008-08-15Martin Stjernholm protected mapping(string:mixed) move_file(string source, string destination, PropertyBehavior behavior, Overwrite overwrite, RequestID id)
ba42f42004-05-13Henrik Grubbström (Grubba) { // 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); }
7406102004-05-13Martin Stjernholm //! Used by the default @[recurse_move_files] to move a collection //! (aka directory) and all its content from @[source] to //! @[destination]. //!
4ba29a2004-05-13Martin Stjernholm //! 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. //!
7406102004-05-13Martin Stjernholm //! @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.
fc40392008-08-15Martin Stjernholm protected mapping(string:mixed) move_collection( string source, string destination, PropertyBehavior behavior, Overwrite overwrite, RequestID id)
ba42f42004-05-13Henrik Grubbström (Grubba) { // 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.
65f9ae2012-01-23Henrik Grubbström (Grubba)  string prefix = map(query_location()[1..]/"/", Roxen.http_encode_url)*"/";
ba42f42004-05-13Henrik Grubbström (Grubba)  MultiStatus.Prefixed result =
65f9ae2012-01-23Henrik Grubbström (Grubba)  id->get_multi_status()->prefix (id->url_base() + prefix);
ba42f42004-05-13Henrik Grubbström (Grubba)  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);
4ba29a2004-05-13Martin Stjernholm  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) {
ba42f42004-05-13Henrik Grubbström (Grubba)  // Failed to move some content. fail = 1; }
4ba29a2004-05-13Martin Stjernholm  }
ba42f42004-05-13Henrik Grubbström (Grubba)  } if (fail) return ([]); return delete_file(source, id); }
7406102004-05-13Martin Stjernholm //! 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()].
7d1c8e2004-05-13Henrik Grubbström (Grubba) mapping(string:mixed) recurse_move_files(string source, string destination, PropertyBehavior behavior, Overwrite overwrite, RequestID id)
ba42f42004-05-13Henrik Grubbström (Grubba) { 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); }
8b8ecf2000-08-28Johan Sundström string real_file(string f, RequestID id){}
b1fca01996-11-12Per Hedbor 
4f4bc11998-02-04Per Hedbor void add_api_function( string name, function f, void|array(string) types) { _api_functions[name] = ({ f, types }); } mapping api_functions() { return _api_functions; }
60b81c2001-01-04Martin Nilsson #if ROXEN_COMPAT <= 1.4
9ed3ea2000-08-06Martin Stjernholm mapping(string:function) query_tag_callers()
facee72000-07-18Johan Sundström //! Compat
48ca161999-05-18Per Hedbor {
9ed3ea2000-08-06Martin Stjernholm  mapping(string:function) m = ([]);
48ca161999-05-18Per Hedbor  foreach(glob("tag_*", indices( this_object())), string q) if(functionp( this_object()[q] )) m[replace(q[4..], "_", "-")] = this_object()[q]; return m; }
9ed3ea2000-08-06Martin Stjernholm mapping(string:function) query_container_callers() //! Compat
48ca161999-05-18Per Hedbor {
9ed3ea2000-08-06Martin Stjernholm  mapping(string:function) m = ([]);
48ca161999-05-18Per Hedbor  foreach(glob("container_*", indices( this_object())), string q) if(functionp( this_object()[q] )) m[replace(q[10..], "_", "-")] = this_object()[q]; return m; }
60b81c2001-01-04Martin Nilsson #endif
48ca161999-05-18Per Hedbor 
9ed3ea2000-08-06Martin Stjernholm mapping(string:array(int|function)) query_simpletag_callers()
7d2a5d2000-01-31Martin Nilsson {
9ed3ea2000-08-06Martin Stjernholm  mapping(string:array(int|function)) m = ([]);
7d2a5d2000-01-31Martin Nilsson  foreach(glob("simpletag_*", indices(this_object())), string q) if(functionp(this_object()[q]))
d6b20a2000-02-08Martin Stjernholm  m[replace(q[10..],"_","-")] = ({ intp (this_object()[q + "_flags"]) && this_object()[q + "_flags"], this_object()[q] });
7d2a5d2000-01-31Martin Nilsson  return m;
48ca161999-05-18Per Hedbor }
ea062e1999-11-21Martin Nilsson 
9ed3ea2000-08-06Martin Stjernholm 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; }
1b2b752000-01-07Martin Stjernholm RXML.TagSet query_tag_set()
b9ec252000-01-05Martin Stjernholm {
395e462000-01-18Martin Stjernholm  if (!module_tag_set) { array(function|program|object) tags = filter (rows (this_object(), glob ("Tag*", indices (this_object()))),
d6b0f82003-01-15Henrik Grubbström (Grubba)  lambda(mixed x) { return functionp(x)||programp(x); });
395e462000-01-18Martin Stjernholm  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 =
dd9a412001-08-24Martin Stjernholm  (this_object()->ModuleTagSet || RXML.TagSet) (this_object(), "", tags);
395e462000-01-18Martin Stjernholm  } return module_tag_set;
b9ec252000-01-05Martin Stjernholm }
12e79e1999-12-07Martin Nilsson mixed get_value_from_file(string path, string index, void|string pre)
ea062e1999-11-21Martin Nilsson {
7fb7f51999-11-29Per Hedbor  Stdio.File file=Stdio.File();
ea062e1999-11-21Martin Nilsson  if(!file->open(path,"r")) return 0;
e4baad2003-08-07Jonas Wallden  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];
ea062e1999-11-21Martin Nilsson }
a730c22001-01-19Per Hedbor 
431a482013-09-26Henrik Grubbström (Grubba) #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,
2e54512016-11-17Henrik Grubbström (Grubba)  int|void max_size, int|void max_files, string|void quarantine)
431a482013-09-26Henrik Grubbström (Grubba) { return roxen.register_fsgarb(module_identifier(), path, max_age,
2e54512016-11-17Henrik Grubbström (Grubba)  max_size, max_files, quarantine);
431a482013-09-26Henrik Grubbström (Grubba) } #endif
fc40392008-08-15Martin Stjernholm private mapping __my_tables = ([]);
2be1982001-08-01Per Hedbor 
2466122001-08-01Per Hedbor array(mapping(string:mixed)) sql_query( string query, mixed ... args )
2be1982001-08-01Per Hedbor //! 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 ); }
2466122001-08-01Per Hedbor object sql_big_query( string query, mixed ... args )
fc40392008-08-15Martin Stjernholm //! Identical to @[sql_query], but the @[Sql.Sql()->big_query] method //! will be used instead of the @[Sql.Sql()->query] method.
2be1982001-08-01Per Hedbor { return get_my_sql()->big_query( replace( query, __my_tables ), @args ); }
2466122001-08-01Per Hedbor 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 )
fc40392008-08-15Martin Stjernholm //! Identical to @[sql_query_ro], but the @[Sql.Sql()->big_query] method //! will be used instead of the @[Sql.Sql()->query] method.
2466122001-08-01Per Hedbor { return get_my_sql(1)->big_query( replace( query, __my_tables ), @args ); }
fc40392008-08-15Martin Stjernholm protected int create_sql_tables( mapping(string:array(string)) definitions, string|void comment, int|void no_unique_names )
2be1982001-08-01Per Hedbor //! Create multiple tables in one go. See @[get_my_table]
3a7e562001-08-23Per Hedbor //! Returns the number of tables that were actually created.
2be1982001-08-01Per Hedbor {
3a7e562001-08-23Per Hedbor  int ddc;
59ccd82001-08-14Per Hedbor  if( !no_unique_names )
6d22f92003-04-23Martin Stjernholm  foreach( indices( definitions ), string t ) ddc+=get_my_table( t, definitions[t], comment, 1 );
59ccd82001-08-14Per Hedbor  else { Sql.Sql sql = get_my_sql();
6d22f92003-04-23Martin Stjernholm  foreach( indices( definitions ), string t )
59ccd82001-08-14Per Hedbor  {
3a7e562001-08-23Per Hedbor  if( !catch {
6d22f92003-04-23Martin Stjernholm  sql->query("CREATE TABLE "+t+" ("+definitions[t]*","+")" );
3a7e562001-08-23Per Hedbor  } ) ddc++;
59ccd82001-08-14Per Hedbor  DBManager.is_module_table( this_object(), my_db, t, comment ); } }
3a7e562001-08-23Per Hedbor  return ddc;
2be1982001-08-01Per Hedbor }
fc40392008-08-15Martin Stjernholm protected string sql_table_exists( string name )
298be12001-08-13Per Hedbor //! Return the real name of the table 'name' if it exists.
2466122001-08-01Per Hedbor { if(strlen(name)) name = "_"+name; string res = hash(_my_configuration->name)->digits(36)
b1ac142005-06-15Anders Johansson  + "_" + replace(sname(), ({ "#","-" }), ({ "_","_" })) + name;
2466122001-08-01Per Hedbor 
8214e62001-08-01Per Hedbor  return catch(get_my_sql()->query( "SELECT * FROM "+res+" LIMIT 1" ))?0:res;
2466122001-08-01Per Hedbor }
fc40392008-08-15Martin Stjernholm protected string|int get_my_table( string|array(string) name, void|array(string)|string definition, string|void comment, int|void flag )
1e9a1b2001-02-21Per Hedbor //! @decl string get_my_table( string name, array(string) types )
c76ebd2004-03-23Martin Stjernholm //! @decl string get_my_table( string name, string definition ) //! @decl string get_my_table( string definition ) //! @decl string get_my_table( array(string) definition )
1e9a1b2001-02-21Per Hedbor //! //! 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.
2be1982001-08-01Per Hedbor //! //! You can use @[create_sql_tables] instead of this function if you want //! to create more than one table in one go.
1e9a1b2001-02-21Per Hedbor //!
3a7e562001-08-23Per Hedbor //! If @[flag] is true, return 1 if a table was created, and 0 otherwise. //!
1e9a1b2001-02-21Per Hedbor //! In the first form, @[name] is the (postfix of) the name of the
6d22f92003-04-23Martin Stjernholm //! table, and @[types] is an array of definitions, as an example:
1e9a1b2001-02-21Per Hedbor //!
3a7e562001-08-23Per Hedbor //!
dd03b22006-04-20Henrik Grubbström (Grubba) //! @code
1e9a1b2001-02-21Per Hedbor //! cache_table = get_my_table( "cache", ({ //! "id INT UNSIGNED AUTO_INCREMENT", //! "data BLOB", //! }) );
dd03b22006-04-20Henrik Grubbström (Grubba) //! @endcode
6533f22001-08-23Martin Nilsson //!
c76ebd2004-03-23Martin Stjernholm //! In the second form, the whole table definition is instead sent as
1e9a1b2001-02-21Per Hedbor //! 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 "" //!
a0487b2001-07-31Per Hedbor //! If the table does not exist in the datbase, it is created.
1e9a1b2001-02-21Per Hedbor //!
6533f22001-08-23Martin Nilsson //! @note //! This function may not be called from create //
c76ebd2004-03-23Martin Stjernholm // 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
a0487b2001-07-31Per Hedbor // 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).
1e9a1b2001-02-21Per Hedbor {
2be1982001-08-01Per Hedbor  string oname;
3a7e562001-08-23Per Hedbor  int ddc;
c76ebd2004-03-23Martin Stjernholm  if( !definition )
1e9a1b2001-02-21Per Hedbor  {
c76ebd2004-03-23Martin Stjernholm  definition = name;
2be1982001-08-01Per Hedbor  oname = name = "";
1e9a1b2001-02-21Per Hedbor  } else if(strlen(name))
2be1982001-08-01Per Hedbor  name = "_"+(oname = name);
a730c22001-01-19Per Hedbor 
a665382001-01-29Per Hedbor  Sql.Sql sql = get_my_sql();
2466122001-08-01Per Hedbor 
1e9a1b2001-02-21Per Hedbor  string res = hash(_my_configuration->name)->digits(36)
b1ac142005-06-15Anders Johansson  + "_" + replace(sname(),({ "#","-" }), ({ "_","_" })) + name;
1e9a1b2001-02-21Per Hedbor 
a665382001-01-29Per Hedbor  if( !sql ) { report_error("Failed to get SQL handle, permission denied for "+my_db+"\n"); return 0; }
c76ebd2004-03-23Martin Stjernholm  if( arrayp( definition ) ) definition *= ", ";
1e9a1b2001-02-21Per Hedbor 
a665382001-01-29Per Hedbor  if( catch(sql->query( "SELECT * FROM "+res+" LIMIT 1" )) )
1e9a1b2001-02-21Per Hedbor  {
3a7e562001-08-23Per Hedbor  ddc++;
1e9a1b2001-02-21Per Hedbor  mixed error = catch {
c76ebd2004-03-23Martin Stjernholm  get_my_sql()->query( "CREATE TABLE "+res+" ("+definition+")" );
298be12001-08-13Per Hedbor  DBManager.is_module_table( this_object(), my_db, res, oname+"\0"+comment );
1e9a1b2001-02-21Per Hedbor  }; if( error ) { if( strlen( name ) ) name = " "+name;
2466122001-08-01Per Hedbor  report_error( "Failed to create table"+name+": "+ describe_error( error ) );
1e9a1b2001-02-21Per Hedbor  return 0; }
3a7e562001-08-23Per Hedbor  if( flag ) { __my_tables[ "&"+oname+";" ] = res; return ddc; }
2be1982001-08-01Per Hedbor  return __my_tables[ "&"+oname+";" ] = res;
1e9a1b2001-02-21Per Hedbor  }
c76ebd2004-03-23Martin Stjernholm // // Update definition if it has changed.
a0487b2001-07-31Per Hedbor // mixed error = // catch // {
c76ebd2004-03-23Martin Stjernholm // get_my_sql()->query( "ALTER TABLE "+res+" ("+definition+")" );
a0487b2001-07-31Per Hedbor // }; // if( error ) // { // if( strlen( name ) ) // name = " for "+name;
c76ebd2004-03-23Martin Stjernholm // report_notice( "Failed to update table definition"+name+": "+
a0487b2001-07-31Per Hedbor // describe_error( error ) ); // }
3a7e562001-08-23Per Hedbor  if( flag ) { __my_tables[ "&"+oname+";" ] = res; return ddc; }
2be1982001-08-01Per Hedbor  return __my_tables[ "&"+oname+";" ] = res;
a730c22001-01-19Per Hedbor }
fc40392008-08-15Martin Stjernholm protected string my_db = "local";
6533f22001-08-23Martin Nilsson 
fc40392008-08-15Martin Stjernholm protected void set_my_db( string to )
1e9a1b2001-02-21Per Hedbor //! 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; }
a31e392006-09-18Martin Stjernholm Sql.Sql get_my_sql( int|void read_only, void|string charset )
1e9a1b2001-02-21Per Hedbor //! Return a SQL-object for the database set with @[set_my_db],
a31e392006-09-18Martin Stjernholm //! 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.
1e9a1b2001-02-21Per Hedbor //! //! See also @[DBManager.get]
a730c22001-01-19Per Hedbor {
a31e392006-09-18Martin Stjernholm  return DBManager.cached_get( my_db, _my_configuration, read_only, charset );
a730c22001-01-19Per Hedbor }
376d412013-11-13Martin Jonsson  // 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);