Branch: Tag:

2004-03-01

2004-03-01 19:09:29 by Henrik Grubbström (Grubba) <grubba@grubba.org>

Improved RFC 2518 compliance with respect to PROPPATCH.

Rev: server/modules/misc/webdav.pike:1.3

1:   // Protocol support for RFC 2518   // - // $Id: webdav.pike,v 1.2 2003/12/22 17:12:18 grubba Exp $ + // $Id: webdav.pike,v 1.3 2004/03/01 19:09:29 grubba Exp $   //   // 2003-09-17 Henrik Grubbström   
9:   #include <module.h>   #include <request_trace.h>    - constant cvs_version = "$Id: webdav.pike,v 1.2 2003/12/22 17:12:18 grubba Exp $"; + constant cvs_version = "$Id: webdav.pike,v 1.3 2004/03/01 19:09:29 grubba Exp $";   constant thread_safe = 1;   constant module_name = "DAV: Protocol support";   constant module_type = MODULE_FIRST;
37:    return ([ "type":"text/html",    "data":"",    "extra_heads":([ -  "Allow":"CHMOD,DELETE,GET,HEAD,MKCOL,MKDIR,MOVE," +  "Allow":"CHMOD,COPY,DELETE,GET,HEAD,MKCOL,MKDIR,MOVE,"    "MV,PING,POST,PROPFIND,PROPPATCH,PUT,OPTIONS", -  "Public":"CHMOD,DELETE,GET,HEAD,MKCOL,MKDIR,MOVE," +  "Public":"CHMOD,COPY,DELETE,GET,HEAD,MKCOL,MKDIR,MOVE,"    "MV,PING,POST,PROPFIND,PROPPATCH,PUT,OPTIONS",    "Accept-Ranges":"bytes",    "DAV":"1",    ]),    ]); -  +  case "COPY":    case "PROPFIND":    case "PROPPATCH":    // These need to be special cased, since they are recursive.
103:    TRACE_LEAVE("Malformed XML.");    return Roxen.http_low_answer(400, "Malformed XML data.");    } -  if (!(<"PROPFIND", "PROPPATCH">)[id->method]) { +  if (!(<"COPY", "PROPFIND", "PROPPATCH">)[id->method]) {    TRACE_LEAVE("Not implemented.");    return Roxen.http_low_answer(501, "Not implemented.");    } -  int depth = ([ "0":0, "1":1, "infinity":0x7fffffff, 0:0x7fffffff ]) +  +  // 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)]; -  +  } else if (id->request_headers->depth) { +  // Depth header not supported in this case. +  }    if (zero_type(depth)) {    TRACE_LEAVE(sprintf("Bad depth header: %O.",    id->request_headers->depth));
124:    // RoxenModule module    // RequestID id    // mixed ... extras -  function(string,int,RoxenModule,MultiStatus,RequestID,mixed ...:void) +  function(string,string,int,RoxenModule,MultiStatus,RequestID,mixed ...:void)    recur_func;    array(mixed) extras = ({});       switch(id->method) { -  +  case "COPY": +  if (!id->request_headers->destination) { +  return Roxen.http_low_answer(400, "COPY: Missing destination header."); +  } +  // Check that destination is on this virtual server. +  Standards.URI dest_uri; +  if (catch { +  dest_uri = Standards.URI(id->request_headers->destination); +  }) { +  return Roxen.http_low_answer(400, +  sprintf("COPY: Bad destination header:%O.", +  id->request_headers->destination)); +  } +  if ((dest_uri->scheme != id->port_obj->prot_name) || +  !(<id->misc->host, id->port_obj->ip, gethostname()>)[dest_uri->host] || +  dest_uri->port != id->port_obj->port) { +  return Roxen.http_low_answer(400, +  sprintf("COPY: Bad destination:%O != %s://%s:%d/.", +  id->request_headers->destination, +  id->port_obj->prot_name, +  id->port_obj->ip, +  id->port_obj->port)); +  } +  extras = ({ dest_uri->path }); +  if (xml_data) { +  // Mapping from href to behavior. +  // @int +  // @value -1 +  // omit +  // @value 0 +  // default +  // If default also check the entry for href @tt{0@} (zero). +  // @value 1 +  // keepalive +  // @endint +  mapping(string:int(-1..1)) propertybehavior = ([]); +  Parser.XML.Tree.Node prop_behav_node = +  xml_data->get_first_element("DAV:propertybehavior", 1); +  if (!prop_behav_node) { +  return Roxen.http_low_answer(400, "Missing DAV:propertybehavior."); +  } +  /* Valid children of <DAV:propertybehavior> are +  * <DAV:keepalive> (12.12.1) +  * or +  * <DAV:omit> (12.12.2) +  */ +  foreach(prop_behav_node->get_children(), Parser.XML.Tree.Node n) { +  switch(n->get_full_name()) { +  case "DAV:omit": +  if (propertybehavior[0] > 0) { +  return Roxen.http_low_answer(400, "Conflicting DAV:propertybehavior."); +  } +  propertybehavior[0] = -1; +  break; +  case "DAV:keepalive": +  foreach(n->get_children(), Parser.XML.Tree.Node href) { +  if (href->get_full_name == "DAV:href") { +  propertybehavior[href->value_of_node()] = 1; +  } else if (href->mNodeType == Parser.XML.Tree.XML_TEXT) { +  if (href->get_text() != "*"){ +  return Roxen.http_low_answer(400, "Syntax error in DAV:keepalive."); +  } +  if (propertybehavior[0] < 0) { +  return Roxen.http_low_answer(400, "Conflicting DAV:propertybehavior."); +  } +  propertybehavior[0] = 1; +  } +  } +  break; +  } +  } +  if (sizeof(propertybehavior)) { +  extras += ({ propertybehavior }); +  } +  } +  +  recur_func = lambda(string path, string dest, int d, RoxenModule module, +  MultiStatus stat, RequestID id, +  mapping(string:int(-1..1))|void behavior) { +  module->recurse_copy_files(path, d, 0, "", +  behavior||([]), +  stat, id); +  }; +  break;    case "PROPFIND": // Get meta data.    if (xml_data) {    Parser.XML.Tree.Node propfind =
148:    case "DAV:propname":    if (recur_func)    return Roxen.http_low_answer(400, "Bad DAV request (23.3.2.1)."); -  recur_func = lambda(string path, int d, RoxenModule module, +  recur_func = lambda(string path, string ignored, int d, +  RoxenModule module,    MultiStatus stat, RequestID id) {    module->recurse_find_properties(path, "DAV:propname", d,    stat, id);
157:    case "DAV:allprop":    if (recur_func)    return Roxen.http_low_answer(400, "Bad DAV request (23.3.2.1)."); -  recur_func = lambda(string path, int d, RoxenModule module, +  recur_func = lambda(string path, string ignored, int d, +  RoxenModule module,    MultiStatus stat, RequestID id,    multiset(string)|void filt) {    module->recurse_find_properties(path, "DAV:allprop", d,
167:    case "DAV:prop":    if (recur_func)    return Roxen.http_low_answer(400, "Bad DAV request (23.3.2.1)."); -  recur_func = lambda(string path, int d, RoxenModule module, +  recur_func = lambda(string path, string ignored, int d, +  RoxenModule module,    MultiStatus stat, RequestID id,    multiset(string) filt) {    module->recurse_find_properties(path, "DAV:prop", d,
197:    // 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. -  recur_func = lambda(string path, int d, RoxenModule module, +  recur_func = lambda(string path, string ignored, int d, +  RoxenModule module,    MultiStatus stat, RequestID id) {    module->recurse_find_properties(path, "DAV:allprop", d,    stat, id);
247:    TRACE_LEAVE("Bad DAV request.");    return Roxen.http_low_answer(400, "Bad DAV request (23.3.2.2).");    } -  recur_func = lambda(string path, int d, RoxenModule module, +  recur_func = lambda(string path, string ignored, int d, RoxenModule module,    MultiStatus stat, RequestID id,    array(PatchPropertyCommand) instructions) { -  module->recurse_patch_properties(path, d, instructions, -  stat, id); +  // NOTE: RFC 2518 does not support depth header +  // with PROPPATCH, thus no recursion wrapper. +  module->patch_properties(path, instructions, stat, id);    };    extras = ({ instructions });    break;
263:    return Roxen.http_low_answer(400, "Bad DAV request (23.3.2.2).");    }    // FIXME: Security, DoS, etc... -  MultiStatus result = -  MultiStatus()->prefix(sprintf("%s://%s%s%s", +  MultiStatus result = MultiStatus(); +  if (id->method != "COPY") { +  result = result->prefix(sprintf("%s://%s%s%s",    id->port_obj->prot_name,    id->misc->host || id->port_obj->ip ||    gethostname(),    (id->port_obj->port == id->port_obj->port)?    "":(":"+(string)id->port_obj->port),    id->port_obj->path||"")); -  +  } else { +  result = result->prefix(id->request_headers->destination); +  }    string href = id->not_query;    string href_prefix = combine_path(href, "./");    foreach(conf->location_modules(), [string loc, function fun]) {    int d = depth; -  string path; +  string path; // Module location relative path. +  string dest = ""; // Destination header relative path.    TRACE_ENTER(sprintf("Trying module mounted at %O...", loc),    function_object(fun)); -  if (has_prefix(href, loc)) { +  if (has_prefix(href, loc) || (loc == href+"/")) {    // href = loc + path.    path = href[sizeof(loc)..];    } else if (d && has_prefix(loc, href_prefix) && -  ((d -= sizeof(loc[sizeof(href_prefix)..]/"/")) >= 0)) { +  ((d -= sizeof((dest = loc[sizeof(href_prefix)..])/"/")) >= 0)) {    // loc = href_path + ...    // && recursion.    path = ""; -  +  if (!sizeof(dest) || (dest[0] != '/')) { +  dest = "/" + dest; +  }    } else {    TRACE_LEAVE("Miss");    continue;
294: Inside #if defined(MODULE_LEVEL_SECURITY)
   int|mapping security_ret;    if(security_ret = conf->check_security(fun, id)) {    if (mappingp(security_ret)) { +  // FIXME: What if we've already added some stuff in result?    TRACE_LEAVE("Security check return.");    TRACE_LEAVE("Need authentication.");    return security_ret;
305:   #endif    RoxenModule c = function_object(fun);    TRACE_ENTER("Performing the work...", c); -  recur_func(path, d, c, result->prefix(loc), id, @extras); +  recur_func(path, dest, d, c, +  id->method=="COPY"?result->prefix(dest):result->prefix(loc), +  id, @extras);    TRACE_LEAVE("Done.");    TRACE_LEAVE("Done.");    }