97e5032018-03-20Henrik Grubbström (Grubba) // Protocol support for RFC 2518 and RFC 4918
5496fd2003-09-17Henrik Grubbström (Grubba) //
97e5032018-03-20Henrik Grubbström (Grubba) // $Id$
5496fd2003-09-17Henrik Grubbström (Grubba) // // 2003-09-17 Henrik Grubbström inherit "module"; #include <module.h> #include <request_trace.h>
97e5032018-03-20Henrik Grubbström (Grubba) constant cvs_version = "$Id$";
5496fd2003-09-17Henrik Grubbström (Grubba) constant thread_safe = 1;
8df8052004-06-01Jonas Wallden constant module_name = "WebDAV: Protocol support";
5496fd2003-09-17Henrik Grubbström (Grubba) constant module_type = MODULE_FIRST; constant module_doc = "Adds support for various HTTP extensions defined " "in <a href='http://rfc.roxen.com/2518'>RFC 2518 (WEBDAV)</a>, such as " "<b>PROPFIND</b> and <b>PROPPATCH</b>.";
ca04922003-12-22Henrik Grubbström (Grubba) #ifdef DAV_DEBUG #define DAV_WERROR(X...) werror(X) #else /* !DAV_DEBUG */ #define DAV_WERROR(X...) #endif /* DAV_DEBUG */
27d8752018-03-27Henrik Grubbström (Grubba) #ifdef IF_HEADER_DEBUG #define IF_HDR_MSG(X...) werror (X) #else #define IF_HDR_MSG(X...) #endif
17a69a2004-05-13Henrik Grubbström (Grubba) // Stuff from base_server/configuration.pike: #ifdef THREADS #define LOCK(X) key=id->conf->_lock(X) #define UNLOCK() do{key=0;}while(0) #else #define LOCK(X) #define UNLOCK() #endif
5496fd2003-09-17Henrik Grubbström (Grubba) Configuration conf;
76c3512004-05-14Henrik Grubbström (Grubba) void create() { defvar( "lock-timeout", 3600, "Default lock timeout", TYPE_INT,
8df8052004-06-01Jonas Wallden  "Number of seconds a WebDAV lock should by default be valid for. "
76c3512004-05-14Henrik Grubbström (Grubba)  "Negative disables locking. Zero means that locks default to " "being valid for infinite duration." ); defvar( "max-lock-timeout", 86400, "Maximum lock timeout", TYPE_INT,
8df8052004-06-01Jonas Wallden  "Maximum number of seconds a WebDAV lock should be valid for. "
76c3512004-05-14Henrik Grubbström (Grubba)  "Negative disables the timeout header. " "Zero enables infinite locks. " ); }
5496fd2003-09-17Henrik Grubbström (Grubba) void start(int q, Configuration c) { conf = c; }
27d8752018-03-27Henrik Grubbström (Grubba) mapping(string:mixed)|int(-1..0) low_check_if_header(string path, array(array(array(string))) condition, RequestID id) { SIMPLE_TRACE_ENTER(this, "Checking \"If\" header for %O", path); string|int(-1..0) etag; mapping(string:DAVLock) locks; next_condition: foreach(condition, array(array(string)) sub_cond) { SIMPLE_TRACE_ENTER(this, "Trying condition ( %{%s:%O %})...", sub_cond); int negate; foreach(sub_cond, array(string) token) { switch(token[0]) { case "not": negate = !negate; break; case "etag": if (!etag) { // Get the etag for this resource (if any). // FIXME: We only support straight strings as etag properties. if (!stringp(etag = id->conf->query_property(path, "DAV:getetag", id))) { etag = -1; } IF_HDR_MSG("IF: Etag: %O.\n", etag); } if (etag != token[1]) { // No etag available for this resource, or mismatch. if (!negate) { TRACE_LEAVE("Etag mismatch."); continue next_condition; } } else if (negate) { // Etag match with negated expression. TRACE_LEAVE("Matched negated etag."); continue next_condition; } negate = 0; break; case "key": if (!locks) { locks = id->conf->find_locks(path, 0, 0, id); } if (negate) { if (locks[token[1]]) { TRACE_LEAVE("Matched negated lock."); continue next_condition; // Fail. } } else if (!locks[token[1]]) { // Lock mismatch. SIMPLE_TRACE_LEAVE("Lock mismatch for key %O.", token[1]); continue next_condition; // Fail. } negate = 0; break; default: SIMPLE_TRACE_LEAVE("Unsupported condition: %s: %O.", token[0], token[1]); continue next_condition; // Fail. } } // Found matching sub-condition. TRACE_LEAVE("Found match."); TRACE_LEAVE("Ok%s."); return 0; } TRACE_LEAVE("Precondition failed."); return Roxen.http_status(Protocols.HTTP.HTTP_PRECOND_FAILED); } //! Checks that all preconditions specified by any if-header hold true. //! //! @returns //! Returns @expr{0@} (zero) if all conditions hold true, and typically //! a @[Protocols.HTTP.HTTP_PRECOND_FAILED] error status if not. mapping(string:mixed)|int(-1..0) check_if_header(RequestID id) { mapping(string:array(array(array(string)))) if_data = id->get_if_data(); if (!if_data) return 0; foreach(if_data; string path; array(array(array(string))) condition) { if (!path) continue; mapping(string:mixed)|int(-1..0) res = low_check_if_header(path, condition, id); if (res) return res; } return 0; }
cf09072004-04-29Henrik Grubbström (Grubba) mapping(string:mixed)|int(-1..0) first_try(RequestID id)
5496fd2003-09-17Henrik Grubbström (Grubba) {
27d8752018-03-27Henrik Grubbström (Grubba)  if (id->misc->internal_get) return 0; mapping(string:mixed)|int(-1..0) res = check_if_header(id); if (res) { return res; }
5496fd2003-09-17Henrik Grubbström (Grubba)  switch(id->method) { case "OPTIONS": return ([ "type":"text/html", "data":"", "extra_heads":([
a378422004-03-01Henrik Grubbström (Grubba)  "Allow":"CHMOD,COPY,DELETE,GET,HEAD,MKCOL,MKDIR,MOVE,"
5496fd2003-09-17Henrik Grubbström (Grubba)  "MV,PING,POST,PROPFIND,PROPPATCH,PUT,OPTIONS",
a378422004-03-01Henrik Grubbström (Grubba)  "Public":"CHMOD,COPY,DELETE,GET,HEAD,MKCOL,MKDIR,MOVE,"
5496fd2003-09-17Henrik Grubbström (Grubba)  "MV,PING,POST,PROPFIND,PROPPATCH,PUT,OPTIONS", "Accept-Ranges":"bytes",
1795852004-05-08Henrik Grubbström (Grubba)  "DAV":"1,2",
5496fd2003-09-17Henrik Grubbström (Grubba)  ]), ]);
cf09072004-04-29Henrik Grubbström (Grubba)  case "LOCK": case "UNLOCK":
a378422004-03-01Henrik Grubbström (Grubba)  case "COPY":
dc50a62004-05-13Henrik Grubbström (Grubba)  case "MOVE":
63a38c2004-03-03Henrik Grubbström (Grubba)  case "DELETE":
5496fd2003-09-17Henrik Grubbström (Grubba)  case "PROPFIND": case "PROPPATCH": // These need to be special cased, since they are recursive. return handle_webdav(id); } return 0; }
fc40392008-08-15Martin Stjernholm protected constant SimpleNode = Parser.XML.Tree.SimpleNode; protected constant SimpleRootNode = Parser.XML.Tree.SimpleRootNode; protected constant SimpleHeaderNode = Parser.XML.Tree.SimpleHeaderNode; protected constant SimpleElementNode = Parser.XML.Tree.SimpleElementNode;
a8e2b32004-05-07Henrik Grubbström (Grubba) 
5496fd2003-09-17Henrik Grubbström (Grubba) //! Implements PROPPATCH <DAV:set/>. class PatchPropertySetCmd {
d5fb852004-03-01Henrik Grubbström (Grubba)  constant command="DAV:set";
5496fd2003-09-17Henrik Grubbström (Grubba)  string property_name;
a8e2b32004-05-07Henrik Grubbström (Grubba)  string|array(SimpleNode) value;
fc40392008-08-15Martin Stjernholm  protected void create(SimpleNode prop_node)
5496fd2003-09-17Henrik Grubbström (Grubba)  { property_name = prop_node->get_full_name(); value = prop_node->get_children(); if ((sizeof(value) == 1) && (value[0]->get_node_type() == Parser.XML.Tree.XML_TEXT)) { // Special case for a single text node. value = value[0]->get_text(); } }
df04242004-03-16Henrik Grubbström (Grubba)  mapping(string:mixed) execute(PropertySet context)
5496fd2003-09-17Henrik Grubbström (Grubba)  {
a2a4372004-03-23Martin Stjernholm #ifdef REQUEST_TRACE RequestID id = context->id; SIMPLE_TRACE_ENTER (0, "Setting property %O to %O", property_name, value); #endif mapping(string:mixed) res = context->set_property(property_name, value);
fb6c432004-03-24Anders Johansson #ifdef REQUEST_TRACE
a2a4372004-03-23Martin Stjernholm  SIMPLE_TRACE_LEAVE (res ? sprintf ("Got status %d: %O", res->error, res->rettext) : "");
fb6c432004-03-24Anders Johansson #endif
a2a4372004-03-23Martin Stjernholm  return res;
5496fd2003-09-17Henrik Grubbström (Grubba)  } } //! Implements PROPPATCH <DAV:remove/>.
d5fb852004-03-01Henrik Grubbström (Grubba) class PatchPropertyRemoveCmd(string property_name)
5496fd2003-09-17Henrik Grubbström (Grubba) {
d5fb852004-03-01Henrik Grubbström (Grubba)  constant command="DAV:remove";
0cb4c42004-03-15Martin Stjernholm 
df04242004-03-16Henrik Grubbström (Grubba)  mapping(string:mixed) execute(PropertySet context)
5496fd2003-09-17Henrik Grubbström (Grubba)  {
a2a4372004-03-23Martin Stjernholm #ifdef REQUEST_TRACE RequestID id = context->id; SIMPLE_TRACE_ENTER (0, "Removing property %O", property_name); #endif mapping(string:mixed) res = context->remove_property(property_name);
fb6c432004-03-24Anders Johansson #ifdef REQUEST_TRACE
a2a4372004-03-23Martin Stjernholm  SIMPLE_TRACE_LEAVE (res ? sprintf ("Got status %d: %O", res->error, res->rettext) : "");
fb6c432004-03-24Anders Johansson #endif
a2a4372004-03-23Martin Stjernholm  return res;
5496fd2003-09-17Henrik Grubbström (Grubba)  } } //! Handle WEBDAV requests.
cf09072004-04-29Henrik Grubbström (Grubba) mapping(string:mixed)|int(-1..0) handle_webdav(RequestID id)
5496fd2003-09-17Henrik Grubbström (Grubba) {
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleNode xml_data;
e9ceb92004-06-02Martin Stjernholm  SIMPLE_TRACE_ENTER(this, "Handle WEBDAV request %s...", id->method);
5496fd2003-09-17Henrik Grubbström (Grubba)  if (catch { xml_data = id->get_xml_data(); }) { // RFC 2518 8: // If a server receives ill-formed XML in a request it MUST reject // the entire request with a 400 (Bad Request). TRACE_LEAVE("Malformed XML.");
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(400, "Malformed XML data.");
5496fd2003-09-17Henrik Grubbström (Grubba)  }
dc50a62004-05-13Henrik Grubbström (Grubba)  if (!(< "LOCK", "UNLOCK", "COPY", "MOVE", "DELETE",
cf09072004-04-29Henrik Grubbström (Grubba)  "PROPFIND", "PROPPATCH">)[id->method]) {
5496fd2003-09-17Henrik Grubbström (Grubba)  TRACE_LEAVE("Not implemented.");
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(501, "Not implemented.");
5496fd2003-09-17Henrik Grubbström (Grubba)  }
a378422004-03-01Henrik Grubbström (Grubba)  // RFC 2518 9.2: // The Depth header is only supported if a method's definition // explicitly provides for such support. int depth; if ((<"PROPFIND", "COPY", "MOVE", "DELETE", "LOCK">)[id->method]) { depth = ([ "0":0, "1":1, "infinity":0x7fffffff, 0:0x7fffffff ]) [id->request_headers->depth && String.trim_whites(id->request_headers->depth)];
63a38c2004-03-03Henrik Grubbström (Grubba)  if (zero_type(depth)) { TRACE_LEAVE(sprintf("Bad depth header: %O.", id->request_headers->depth));
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(400, "Unsupported depth.");
63a38c2004-03-03Henrik Grubbström (Grubba)  }
97e5032018-03-20Henrik Grubbström (Grubba)  switch(id->method) { case "DELETE": case "MOVE": // RFC 4918 9.6.1: // The DELETE method on a collection MUST act as if a "Depth: // infinity" header was used on it. A client MUST NOT submit a // Depth header with a DELETE on a collection with any value but // infinity. // // RFC 4918 9.9.2: // The MOVE method on a collection MUST act as if a "Depth: // infinity" header was used on it. A client MUST NOT submit a // Depth header on a MOVE on a collection with any value but // "infinity". if (depth != 0x7fffffff) { TRACE_LEAVE(sprintf("Bad depth header: %O.", id->request_headers->depth)); return Roxen.http_status(400, "Unsupported depth."); } break; case "COPY": // RFC 4918 9.8.3: // The COPY method on a collection without a Depth header MUST // act as if a Depth header with value "infinity" was // included. A client may submit a Depth header on a COPY on a // collection with a value of "0" or "infinity". Servers MUST // support the "0" and "infinity" Depth header behaviors on // WebDAV-compliant resources. if (depth == 1) { TRACE_LEAVE(sprintf("Bad depth header: %O.", id->request_headers->depth)); return Roxen.http_status(400, "Unsupported depth."); } break; }
a378422004-03-01Henrik Grubbström (Grubba)  } else if (id->request_headers->depth) { // Depth header not supported in this case. }
5496fd2003-09-17Henrik Grubbström (Grubba) 
17a69a2004-05-13Henrik Grubbström (Grubba) #ifdef URL_MODULES // Check URL modules (eg htaccess). // Ripped from base_server/configuration.pike. foreach(conf->url_modules(), function funp) { PROF_ENTER(Roxen.get_owning_module(funp)->module_name,"url module"); #ifdef THREADS Thread.MutexKey key; #endif LOCK(funp); TRACE_ENTER("URL module", funp); mapping(string:mixed)|object tmp=funp( id, id->not_query ); UNLOCK(); PROF_LEAVE(Roxen.get_owning_module(funp)->module_name,"url module"); if(mappingp(tmp)) { TRACE_LEAVE(""); TRACE_LEAVE("Returning data"); return tmp; } if(objectp( tmp )) { mixed err; err = catch { tmp = id->conf->low_get_file( tmp, 1 ); }; if(err) throw(err); TRACE_LEAVE(""); TRACE_LEAVE("Returning data"); return tmp; } TRACE_LEAVE(""); } #endif
5496fd2003-09-17Henrik Grubbström (Grubba)  // Function to call for matching location modules. // // Arguments: // string path // int d // RoxenModule module // RequestID id // mixed ... extras
1108142004-05-10Martin Stjernholm  function(string,string,int,RoxenModule,RequestID,
c2025d2004-03-03Henrik Grubbström (Grubba)  mixed ...:mapping(string:mixed)) recur_func;
5496fd2003-09-17Henrik Grubbström (Grubba)  array(mixed) extras = ({});
63a38c2004-03-03Henrik Grubbström (Grubba)  mapping(string:mixed) empty_result;
5496fd2003-09-17Henrik Grubbström (Grubba)  switch(id->method) {
cf09072004-04-29Henrik Grubbström (Grubba)  case "LOCK":
103d3f2004-04-30Henrik Grubbström (Grubba)  DAVLock lock;
cf09072004-04-29Henrik Grubbström (Grubba)  if (!xml_data) { // Refresh.
42e8992004-05-07Martin Stjernholm  int/*LockFlag*/|DAVLock state =
3563702004-05-03Henrik Grubbström (Grubba)  id->conf->check_locks(id->not_query, 0, id);
103d3f2004-04-30Henrik Grubbström (Grubba)  if (intp(state)) { if (state) {
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("LOCK: Refresh: locked by other user.");
103d3f2004-04-30Henrik Grubbström (Grubba)  return Roxen.http_status(423, "Locked by other user"); } else {
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("LOCK: Refresh: Lock not found.");
103d3f2004-04-30Henrik Grubbström (Grubba)  return Roxen.http_status(424, "Couldn't refresh missing lock."); } }
7e8b922004-05-14Henrik Grubbström (Grubba)  id->conf->refresh_lock(lock = state);
cf09072004-04-29Henrik Grubbström (Grubba)  } else { // New lock.
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleNode lock_info_node =
cf09072004-04-29Henrik Grubbström (Grubba)  xml_data->get_first_element("DAV:lockinfo", 1); if (!lock_info_node) {
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("LOCK: No DAV:lockinfo.");
103d3f2004-04-30Henrik Grubbström (Grubba)  return Roxen.http_status(422, "Missing DAV:lockinfo.");
cf09072004-04-29Henrik Grubbström (Grubba)  }
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleNode lock_scope_node =
cf09072004-04-29Henrik Grubbström (Grubba)  lock_info_node->get_first_element("DAV:lockscope", 1); if (!lock_scope_node) {
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("LOCK: No DAV:lockscope.");
103d3f2004-04-30Henrik Grubbström (Grubba)  return Roxen.http_status(422, "Missing DAV:lockscope.");
cf09072004-04-29Henrik Grubbström (Grubba)  } string lockscope; if (lock_scope_node->get_first_element("DAV:exclusive", 1)) { lockscope = "DAV:exclusive"; } if (lock_scope_node->get_first_element("DAV:shared", 1)) { if (lockscope) {
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("LOCK: Both DAV:exclusive and DAV:shared.");
103d3f2004-04-30Henrik Grubbström (Grubba)  return Roxen.http_status(422, "Both DAV:exclusive and DAV:shared.");
cf09072004-04-29Henrik Grubbström (Grubba)  } lockscope = "DAV:shared"; } if (!lockscope) {
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("LOCK: Both DAV:lockscope.");
103d3f2004-04-30Henrik Grubbström (Grubba)  return Roxen.http_status(422, "Unsupported DAV:lockscope.");
cf09072004-04-29Henrik Grubbström (Grubba)  }
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleNode lock_type_node =
cf09072004-04-29Henrik Grubbström (Grubba)  lock_info_node->get_first_element("DAV:locktype", 1); if (!lock_type_node) {
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("LOCK: Both DAV:locktype.");
103d3f2004-04-30Henrik Grubbström (Grubba)  return Roxen.http_status(422, "Missing DAV:locktype.");
cf09072004-04-29Henrik Grubbström (Grubba)  } if (!lock_type_node->get_first_element("DAV:write", 1)) { // We only support DAV:write locks.
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("LOCK: No DAV:write.");
103d3f2004-04-30Henrik Grubbström (Grubba)  return Roxen.http_status(422, "Missing DAV:write.");
cf09072004-04-29Henrik Grubbström (Grubba)  } string locktype = "DAV:write";
bd95642004-05-06Martin Stjernholm 
76c3512004-05-14Henrik Grubbström (Grubba)  int expiry_delta = query("lock-timeout"); // Default timeout. if (expiry_delta < 0) { // Locks disabled. TRACE_LEAVE("LOCK: Locks disabled."); return Roxen.http_status(403, "Locking not allowed."); } if (id->request_headers->timeout) { // Parse the timeout header, and use the first valid timeout. foreach(MIME.tokenize(id->request_headers->timeout), string|int entry) { if (intp(entry)) continue; // ',' etc. if (entry == "Infinite") { if (!query("max-lock-timeout")) { expiry_delta = 0; break; } } else if (has_prefix(entry, "Second-")) { int t; if (sscanf(entry, "Second-%d", t) && (t > 0)) { if (!query("max-lock-timeout") || (t < query("max-lock-timeout"))) { expiry_delta = t; break; } } } } }
a8e2b32004-05-07Henrik Grubbström (Grubba)  array(SimpleNode) owner; if (SimpleNode owner_node = lock_info_node->get_first_element("DAV:owner", 1))
bd95642004-05-06Martin Stjernholm  owner = owner_node->get_children();
103d3f2004-04-30Henrik Grubbström (Grubba)  // Parameters OK, try to create a lock.
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_ENTER(sprintf("LOCK: Creating a %s%s lock on %O.", depth?"recursive ":"", lockscope, id->not_query), this);
103d3f2004-04-30Henrik Grubbström (Grubba)  mapping(string:mixed)|DAVLock new_lock = id->conf->lock_file(id->not_query, depth != 0,
3563702004-05-03Henrik Grubbström (Grubba)  lockscope, "DAV:write",
76c3512004-05-14Henrik Grubbström (Grubba)  expiry_delta,
bd95642004-05-06Martin Stjernholm  owner,
103d3f2004-04-30Henrik Grubbström (Grubba)  id); if (mappingp(new_lock)) { // Error // FIXME: Should probably generate a MultiStatus response. // cf RFC 2518 8.10.10.
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("Failed."); TRACE_LEAVE("Returning failure code.");
103d3f2004-04-30Henrik Grubbström (Grubba)  return new_lock; }
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("Ok.");
103d3f2004-04-30Henrik Grubbström (Grubba)  lock = new_lock;
cf09072004-04-29Henrik Grubbström (Grubba)  }
a8e2b32004-05-07Henrik Grubbström (Grubba)  string xml = SimpleRootNode()-> add_child(SimpleHeaderNode((["version":"1.0", "encoding":"utf-8"])))-> add_child(SimpleElementNode("DAV:prop", ([ "xmlns:DAV":"DAV:", "xmlns:MS": "urn:schemas-microsoft-com:datatypes" ]))-> add_child(SimpleElementNode("DAV:lockdiscovery", ([]))-> add_child(lock->get_xml())))->render_xml();
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("Returning XML.");
103d3f2004-04-30Henrik Grubbström (Grubba)  return ([ "error":200, "data":xml, "len":sizeof(xml), "type":"text/xml; charset=\"utf-8\"",
c057eb2004-05-06Henrik Grubbström (Grubba)  "extra_heads":([ "Lock-Token":lock->locktoken ]),
103d3f2004-04-30Henrik Grubbström (Grubba)  ]);
cf09072004-04-29Henrik Grubbström (Grubba)  case "UNLOCK":
6b34222004-05-04Henrik Grubbström (Grubba)  string locktoken; if (!(locktoken = id->request_headers["lock-token"])) {
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("UNLOCK: No lock-token.");
6b34222004-05-04Henrik Grubbström (Grubba)  return Roxen.http_status(400, "UNLOCK: Missing lock-token header."); } // The lock-token header is a Coded-URL. sscanf(locktoken, "<%s>", locktoken);
7ee0b02018-03-27Henrik Grubbström (Grubba)  string path = id->not_query; if (!has_suffix(path, "/")) path += "/"; mapping(string:DAVLock) locks = id->conf->find_locks(path, 0, 1, id); if (!(lock = locks[locktoken])) {
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE(sprintf("UNLOCK: Lock-token %O not found.", locktoken));
6b34222004-05-04Henrik Grubbström (Grubba)  return Roxen.http_status(403, "UNLOCK: Lock not found."); }
7ee0b02018-03-27Henrik Grubbström (Grubba)  if (lock->path != path) { SIMPLE_TRACE_LEAVE("UNLOCK: Lock doesn't match path: %O != %O.\n", path, lock->path);
6761032004-05-10Henrik Grubbström (Grubba)  return Roxen.http_status(423, "Invalid locktoken."); }
7ee0b02018-03-27Henrik Grubbström (Grubba)  mapping res = id->conf->unlock_file(path, lock, id);
c280752004-05-04Henrik Grubbström (Grubba)  if (res) { TRACE_LEAVE(sprintf("UNLOCK: Unlocking of %O failed.", locktoken)); return res; } TRACE_LEAVE(sprintf("UNLOCK: Unlocked %O successfully.", locktoken)); return Roxen.http_status(204, "Ok.");
a378422004-03-01Henrik Grubbström (Grubba)  case "COPY":
dc50a62004-05-13Henrik Grubbström (Grubba)  case "MOVE":
a378422004-03-01Henrik Grubbström (Grubba)  if (!id->request_headers->destination) {
dc50a62004-05-13Henrik Grubbström (Grubba)  SIMPLE_TRACE_LEAVE("%s: No destination header.", id->method); return Roxen.http_status(400, sprintf("%s: Missing destination header.", id->method));
a378422004-03-01Henrik Grubbström (Grubba)  }
dfc1ad2004-05-13Henrik Grubbström (Grubba)  PropertyBehavior propertybehavior = (<>); // default
a378422004-03-01Henrik Grubbström (Grubba)  if (xml_data) { // Mapping from href to behavior. // @int // @value -1 // omit // @value 0 // default
1795852004-05-08Henrik Grubbström (Grubba)  // If default also check the entry for href @expr{0@} (zero).
a378422004-03-01Henrik Grubbström (Grubba)  // @value 1 // keepalive // @endint
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleNode prop_behav_node =
a378422004-03-01Henrik Grubbström (Grubba)  xml_data->get_first_element("DAV:propertybehavior", 1); if (!prop_behav_node) {
dc50a62004-05-13Henrik Grubbström (Grubba)  SIMPLE_TRACE_LEAVE("%s: No DAV:propertybehavior.", id->method);
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(400, "Missing DAV:propertybehavior.");
a378422004-03-01Henrik Grubbström (Grubba)  } /* Valid children of <DAV:propertybehavior> are * <DAV:keepalive> (12.12.1) * or * <DAV:omit> (12.12.2) */
a8e2b32004-05-07Henrik Grubbström (Grubba)  foreach(prop_behav_node->get_children(), SimpleNode n) {
a378422004-03-01Henrik Grubbström (Grubba)  switch(n->get_full_name()) { case "DAV:omit":
dfc1ad2004-05-13Henrik Grubbström (Grubba)  if (!multisetp(propertybehavior) || sizeof(propertybehavior)) {
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(400, "Conflicting DAV:propertybehavior.");
a378422004-03-01Henrik Grubbström (Grubba)  }
dfc1ad2004-05-13Henrik Grubbström (Grubba)  propertybehavior = 0;
a378422004-03-01Henrik Grubbström (Grubba)  break; case "DAV:keepalive":
dfc1ad2004-05-13Henrik Grubbström (Grubba)  if (!multisetp(propertybehavior) || sizeof(propertybehavior)) { return Roxen.http_status(400, "Conflicting DAV:propertybehavior."); }
a8e2b32004-05-07Henrik Grubbström (Grubba)  foreach(n->get_children(), SimpleNode href) {
a378422004-03-01Henrik Grubbström (Grubba)  if (href->get_full_name == "DAV:href") {
dfc1ad2004-05-13Henrik Grubbström (Grubba)  if (!multisetp(propertybehavior)) {
dc50a62004-05-13Henrik Grubbström (Grubba)  SIMPLE_TRACE_LEAVE("%s: Conflicting DAV:propertybehaviour.", id->method);
dfc1ad2004-05-13Henrik Grubbström (Grubba)  return Roxen.http_status(400, "Conflicting DAV:propertybehavior."); }
a378422004-03-01Henrik Grubbström (Grubba)  propertybehavior[href->value_of_node()] = 1; } else if (href->mNodeType == Parser.XML.Tree.XML_TEXT) { if (href->get_text() != "*"){
dc50a62004-05-13Henrik Grubbström (Grubba)  SIMPLE_TRACE_LEAVE("%s: Syntax error in DAV:keepalive.", id->method);
dfc1ad2004-05-13Henrik Grubbström (Grubba)  return Roxen.http_status(400, "Syntax error in DAV:keepalive.");
a378422004-03-01Henrik Grubbström (Grubba)  }
dfc1ad2004-05-13Henrik Grubbström (Grubba)  if (!multisetp(propertybehavior) || sizeof(propertybehavior)) {
dc50a62004-05-13Henrik Grubbström (Grubba)  SIMPLE_TRACE_LEAVE("%s: Conflicting DAV:propertybehaviour.", id->method);
dfc1ad2004-05-13Henrik Grubbström (Grubba)  return Roxen.http_status(400, "Conflicting DAV:propertybehavior.");
a378422004-03-01Henrik Grubbström (Grubba)  }
dfc1ad2004-05-13Henrik Grubbström (Grubba)  propertybehavior = 1;
a378422004-03-01Henrik Grubbström (Grubba)  } } break; } } }
545f5f2004-05-10Henrik Grubbström (Grubba)  extras = ({ id->misc["new-uri"], propertybehavior,
aaed922004-05-13Martin Stjernholm  // RFC 2518 9.6: If the overwrite header is not // included in a COPY or MOVE request then the // resource [sic] MUST treat the request as if it has // an overwrite header of value "T".
dc50a62004-05-13Henrik Grubbström (Grubba)  (!id->request_headers->overwrite || id->request_headers->overwrite=="T")? DO_OVERWRITE:NEVER_OVERWRITE,
545f5f2004-05-10Henrik Grubbström (Grubba)  });
dc50a62004-05-13Henrik Grubbström (Grubba) 
1795852004-05-08Henrik Grubbström (Grubba)  recur_func = lambda(string source, string loc, int d, RoxenModule module,
1108142004-05-10Martin Stjernholm  RequestID id, string destination,
dfc1ad2004-05-13Henrik Grubbström (Grubba)  PropertyBehavior behavior,
aaed922004-05-13Martin Stjernholm  Overwrite overwrite) {
1795852004-05-08Henrik Grubbström (Grubba)  if (!has_prefix(destination, loc)) { // FIXME: Destination in other filesystem. return 0; } // Convert destination to module location relative. destination = destination[sizeof(loc)..];
97e5032018-03-20Henrik Grubbström (Grubba)  mapping(string:mixed) res; if (id->method == "MOVE") { res = module->recurse_move_files(source, destination, behavior, overwrite, id); } else { res = module->recurse_copy_files(source, destination, behavior, overwrite, id, !d); }
d5432b2004-05-10Henrik Grubbström (Grubba)  if (res && ((res->error == 201) || (res->error == 204))) { empty_result = res;
86ed0b2018-02-02Henrik Grubbström (Grubba)  if (id->method == "MOVE") { // RFC 4918 9.9: // The MOVE operation on a non-collection resource is // the logical equivalent of a copy (COPY), followed // by consistency maintenance processing, followed by // a delete of the source, where all three actions are // performed in a single operation. // The above seems to imply that RFC 4918 9.6 applies // to MOVE. We thus need to destroy any locks rooted // on the moved resource.
da60482018-03-27Henrik Grubbström (Grubba)  mapping(string:DAVLock) sub_locks =
52ab682018-02-02Jonas Walldén  module->find_locks(source, -1, 0, id);
da60482018-03-27Henrik Grubbström (Grubba)  foreach(sub_locks||([]);;DAVLock lock) {
86ed0b2018-02-02Henrik Grubbström (Grubba)  SIMPLE_TRACE_ENTER(module, "MOVE: Unlocking %O...", lock); mapping fail = id->conf->unlock_file(lock->path, lock, id); if (fail) { TRACE_LEAVE("MOVE: Unlock failed."); } else { TRACE_LEAVE("MOVE: Unlock ok."); } } }
d5432b2004-05-10Henrik Grubbström (Grubba)  return 0; }
b629f72004-05-13Martin Stjernholm  else if (!res && id->misc->error_code) { empty_result = Roxen.http_status(id->misc->error_code); }
d5432b2004-05-10Henrik Grubbström (Grubba)  return res;
a378422004-03-01Henrik Grubbström (Grubba)  };
d5432b2004-05-10Henrik Grubbström (Grubba)  empty_result = Roxen.http_status(404);
a378422004-03-01Henrik Grubbström (Grubba)  break;
63a38c2004-03-03Henrik Grubbström (Grubba)  case "DELETE":
1795852004-05-08Henrik Grubbström (Grubba)  recur_func = lambda(string path, string ignored, int d, RoxenModule module,
1108142004-05-10Martin Stjernholm  RequestID id) { mapping res = module->recurse_delete_files(path, id);
6761032004-05-10Henrik Grubbström (Grubba)  if (res && res->error < 300) {
1795852004-05-08Henrik Grubbström (Grubba)  // Succeed in deleting some file(s).
6761032004-05-10Henrik Grubbström (Grubba)  empty_result = res;
69a7f22018-02-01Henrik Grubbström (Grubba)  // RFC 4918 9.6: // A server processing a successful DELETE request: // // MUST destroy locks rooted on the deleted resource
da60482018-03-27Henrik Grubbström (Grubba)  mapping(string:DAVLock) sub_locks =
69a7f22018-02-01Henrik Grubbström (Grubba)  module->find_locks(path, -1, 0, id);
da60482018-03-27Henrik Grubbström (Grubba)  foreach(sub_locks||([]);;DAVLock lock) {
69a7f22018-02-01Henrik Grubbström (Grubba)  SIMPLE_TRACE_ENTER(module, "DELETE: Unlocking %O...", lock); mapping fail = id->conf->unlock_file(lock->path, lock, id); if (fail) { TRACE_LEAVE("DELETE: Unlock failed."); } else { TRACE_LEAVE("DELETE: Unlock ok."); } }
6761032004-05-10Henrik Grubbström (Grubba)  return 0;
1795852004-05-08Henrik Grubbström (Grubba)  }
6761032004-05-10Henrik Grubbström (Grubba)  return res;
63a38c2004-03-03Henrik Grubbström (Grubba)  };
1795852004-05-08Henrik Grubbström (Grubba)  // The multi status will be empty if everything went well, // or if the file didn't exist. empty_result = Roxen.http_status(404);
63a38c2004-03-03Henrik Grubbström (Grubba)  break;
5496fd2003-09-17Henrik Grubbström (Grubba)  case "PROPFIND": // Get meta data. if (xml_data) {
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleNode propfind = xml_data->get_first_element("DAV:propfind", 1);
5496fd2003-09-17Henrik Grubbström (Grubba)  if (!propfind) {
951dce2004-05-10Henrik Grubbström (Grubba)  TRACE_LEAVE("PROPFIND: No DAV:propfind.");
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(400, "Missing DAV:propfind.");
5496fd2003-09-17Henrik Grubbström (Grubba)  } /* Valid children of <DAV:propfind> are * <DAV:propname /> * or * <DAV:allprop /> * or * <DAV:prop>{propertylist}*</DAV:prop> */
a8e2b32004-05-07Henrik Grubbström (Grubba)  foreach(propfind->get_children(), SimpleNode prop) {
5496fd2003-09-17Henrik Grubbström (Grubba)  switch(prop->get_full_name()) { case "DAV:propname":
c280752004-05-04Henrik Grubbström (Grubba)  if (recur_func) { TRACE_LEAVE("PROPFIND: Conflicting DAV:propfind.");
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(400, "Bad DAV request (23.3.2.1).");
c280752004-05-04Henrik Grubbström (Grubba)  }
a378422004-03-01Henrik Grubbström (Grubba)  recur_func = lambda(string path, string ignored, int d,
1108142004-05-10Martin Stjernholm  RoxenModule module, RequestID id) {
e9ceb92004-06-02Martin Stjernholm  return module->recurse_find_properties(path, "DAV:propname", d, id);
5496fd2003-09-17Henrik Grubbström (Grubba)  }; break; case "DAV:allprop":
c280752004-05-04Henrik Grubbström (Grubba)  if (recur_func) { TRACE_LEAVE("PROPFIND: Conflicting DAV:propfind.");
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(400, "Bad DAV request (23.3.2.1).");
c280752004-05-04Henrik Grubbström (Grubba)  }
a378422004-03-01Henrik Grubbström (Grubba)  recur_func = lambda(string path, string ignored, int d,
1108142004-05-10Martin Stjernholm  RoxenModule module, RequestID id,
5496fd2003-09-17Henrik Grubbström (Grubba)  multiset(string)|void filt) {
e9ceb92004-06-02Martin Stjernholm  return module->recurse_find_properties(path, "DAV:allprop", d, id, filt);
5496fd2003-09-17Henrik Grubbström (Grubba)  }; break; case "DAV:prop":
c280752004-05-04Henrik Grubbström (Grubba)  if (recur_func) { TRACE_LEAVE("PROPFIND: Conflicting DAV:propfind.");
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(400, "Bad DAV request (23.3.2.1).");
c280752004-05-04Henrik Grubbström (Grubba)  }
a378422004-03-01Henrik Grubbström (Grubba)  recur_func = lambda(string path, string ignored, int d,
1108142004-05-10Martin Stjernholm  RoxenModule module, RequestID id,
5496fd2003-09-17Henrik Grubbström (Grubba)  multiset(string) filt) {
e9ceb92004-06-02Martin Stjernholm  return module->recurse_find_properties(path, "DAV:prop", d, id, filt);
5496fd2003-09-17Henrik Grubbström (Grubba)  }; // FALL_THROUGH case "http://sapportals.com/xmlns/cm/webdavinclude": // Support for draft-reschke-webdav-allprop-include-04 // FIXME: Should we check that // http://sapportals.com/xmlns/cm/webdavinclude only // occurrs in the DAV:allprop case? multiset(string) props = (multiset(string))(prop->get_children()->get_full_name() - ({ "" })); if (sizeof(extras)) { extras[0] |= props; } else { extras = ({ props }); } break; default: break; } } } else { // RFC 2518 8.1: // A client may choose not to submit a request body. An empty // PROPFIND request body MUST be treated as a request for the // names and values of all properties.
a378422004-03-01Henrik Grubbström (Grubba)  recur_func = lambda(string path, string ignored, int d,
1108142004-05-10Martin Stjernholm  RoxenModule module, RequestID id) {
e9ceb92004-06-02Martin Stjernholm  return module->recurse_find_properties(path, "DAV:allprop", d, id);
5496fd2003-09-17Henrik Grubbström (Grubba)  }; } break; case "PROPPATCH": // Set/delete meta data.
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleNode propupdate = xml_data->get_first_element("DAV:propertyupdate", 1);
5496fd2003-09-17Henrik Grubbström (Grubba)  if (!propupdate) {
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("PROPPATCH: No DAV:propertyupdate.");
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(400, "Missing DAV:propertyupdate.");
5496fd2003-09-17Henrik Grubbström (Grubba)  } /* Valid children of <DAV:propertyupdate> are any combinations of * <DAV:set><DAV:prop>{propertylist}*</DAV:prop></DAV:set> * and * <DAV:remove><DAV:prop>{propertylist}*</DAV:prop></DAV:remove> * * RFC 2518 8.2: * Instruction processing MUST occur in the order instructions * are received (i.e., from top to bottom). */ array(PatchPropertyCommand) instructions = ({});
a8e2b32004-05-07Henrik Grubbström (Grubba)  foreach(propupdate->get_children(), SimpleNode cmd) {
5496fd2003-09-17Henrik Grubbström (Grubba)  switch(cmd->get_full_name()) { case "DAV:set": case "DAV:remove":
a8e2b32004-05-07Henrik Grubbström (Grubba)  SimpleNode prop = cmd->get_first_element("DAV:prop", 1);
5496fd2003-09-17Henrik Grubbström (Grubba)  if (!prop) {
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("PROPPATCH: No DAV:prop.");
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(400, "Bad DAV request (no properties specified).");
5496fd2003-09-17Henrik Grubbström (Grubba)  } if (cmd->get_full_name() == "DAV:set") { instructions += map(prop->get_children(), PatchPropertySetCmd); } else { // FIXME: Should we verify that the properties are empty? instructions += map(prop->get_children()->get_full_name(), PatchPropertyRemoveCmd); } break; default: // FIXME: Should we complain here? break; } } if (!sizeof(instructions)) {
c280752004-05-04Henrik Grubbström (Grubba)  TRACE_LEAVE("PROPPATCH: No instructions.");
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(400, "Bad DAV request (23.3.2.2).");
5496fd2003-09-17Henrik Grubbström (Grubba)  }
a378422004-03-01Henrik Grubbström (Grubba)  recur_func = lambda(string path, string ignored, int d, RoxenModule module,
1108142004-05-10Martin Stjernholm  RequestID id,
5496fd2003-09-17Henrik Grubbström (Grubba)  array(PatchPropertyCommand) instructions) {
a378422004-03-01Henrik Grubbström (Grubba)  // NOTE: RFC 2518 does not support depth header // with PROPPATCH, thus no recursion wrapper.
1108142004-05-10Martin Stjernholm  return module->patch_properties(path, instructions, id);
5496fd2003-09-17Henrik Grubbström (Grubba)  }; extras = ({ instructions }); break; default: break; } if (!recur_func) { TRACE_LEAVE("Bad DAV request.");
a2a4372004-03-23Martin Stjernholm  return Roxen.http_status(400, "Bad DAV request (23.3.2.2).");
5496fd2003-09-17Henrik Grubbström (Grubba)  } // FIXME: Security, DoS, etc...
9c5c122004-05-05Martin Stjernholm  int start_ms_size = id->multi_status_size();
5496fd2003-09-17Henrik Grubbström (Grubba)  string href = id->not_query; string href_prefix = combine_path(href, "./");
b8f6272010-06-28Martin Jonsson  RoxenModule opaque_module;
5496fd2003-09-17Henrik Grubbström (Grubba)  foreach(conf->location_modules(), [string loc, function fun]) { int d = depth;
a378422004-03-01Henrik Grubbström (Grubba)  string path; // Module location relative path.
1795852004-05-08Henrik Grubbström (Grubba)  SIMPLE_TRACE_ENTER(function_object(fun), "Trying module mounted at %O...", loc);
a378422004-03-01Henrik Grubbström (Grubba)  if (has_prefix(href, loc) || (loc == href+"/")) {
5496fd2003-09-17Henrik Grubbström (Grubba)  // href = loc + path. path = href[sizeof(loc)..];
97e5032018-03-20Henrik Grubbström (Grubba)  } else if (d && has_prefix(loc, href_prefix)) {
1795852004-05-08Henrik Grubbström (Grubba)  // loc = href_prefix + ...
97e5032018-03-20Henrik Grubbström (Grubba)  d -= sizeof((loc[sizeof(href_prefix)..])/"/"); if (d < 0) continue;
5496fd2003-09-17Henrik Grubbström (Grubba)  // && recursion. path = ""; } else { TRACE_LEAVE("Miss"); continue; } #ifdef MODULE_LEVEL_SECURITY int|mapping security_ret; if(security_ret = conf->check_security(fun, id)) { if (mappingp(security_ret)) {
a378422004-03-01Henrik Grubbström (Grubba)  // FIXME: What if we've already added some stuff in result?
5496fd2003-09-17Henrik Grubbström (Grubba)  TRACE_LEAVE("Security check return."); TRACE_LEAVE("Need authentication.");
9c5c122004-05-05Martin Stjernholm  recur_func = 0; // Avoid garbage.
5496fd2003-09-17Henrik Grubbström (Grubba)  return security_ret; } else { TRACE_LEAVE("Not allowed."); continue; } } #endif RoxenModule c = function_object(fun);
b8f6272010-06-28Martin Jonsson  if (c->webdav_opaque) opaque_module = c;
5496fd2003-09-17Henrik Grubbström (Grubba)  TRACE_ENTER("Performing the work...", c);
9c5c122004-05-05Martin Stjernholm  ASSERT_IF_DEBUG (has_prefix (loc, "/"));
1108142004-05-10Martin Stjernholm  mapping(string:mixed) ret = recur_func(path, loc, d, c, id, @extras);
c2025d2004-03-03Henrik Grubbström (Grubba)  if (ret) { TRACE_LEAVE("Short circuit return."); TRACE_LEAVE("Done."); TRACE_LEAVE("DAV request done.");
9c5c122004-05-05Martin Stjernholm  recur_func = 0; // Avoid garbage.
c2025d2004-03-03Henrik Grubbström (Grubba)  return ret; }
5496fd2003-09-17Henrik Grubbström (Grubba)  TRACE_LEAVE("Done."); TRACE_LEAVE("Done.");
b8f6272010-06-28Martin Jonsson  if (opaque_module) break;
5496fd2003-09-17Henrik Grubbström (Grubba)  }
b8f6272010-06-28Martin Jonsson  if (opaque_module) TRACE_LEAVE(sprintf("DAV request done (returned early after %O).", opaque_module)); else TRACE_LEAVE("DAV request done.");
9c5c122004-05-05Martin Stjernholm  recur_func = 0; // Avoid garbage. if (id->multi_status_size() == start_ms_size) {
a2a4372004-03-23Martin Stjernholm  return empty_result || Roxen.http_status(404);
63a38c2004-03-03Henrik Grubbström (Grubba)  }
9c5c122004-05-05Martin Stjernholm  return ([]);
5496fd2003-09-17Henrik Grubbström (Grubba) }