Roxen.git / server / modules / misc / webdav.pike

version» Context lines:

Roxen.git/server/modules/misc/webdav.pike:1: - // Protocol support for RFC 2518 + // Protocol support for RFC 2518 and RFC 4918   //   // $Id$   //   // 2003-09-17 Henrik Grubbström      inherit "module";      #include <module.h>   #include <request_trace.h>   
Roxen.git/server/modules/misc/webdav.pike:16:   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>.";      #ifdef DAV_DEBUG   #define DAV_WERROR(X...) werror(X)   #else /* !DAV_DEBUG */   #define DAV_WERROR(X...)   #endif /* DAV_DEBUG */    + #ifdef IF_HEADER_DEBUG + #define IF_HDR_MSG(X...) werror (X) + #else + #define IF_HDR_MSG(X...) + #endif +    // 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      
Roxen.git/server/modules/misc/webdav.pike:45:    "Maximum number of seconds a WebDAV lock should be valid for. "    "Negative disables the timeout header. "    "Zero enables infinite locks. " );   }      void start(int q, Configuration c)   {    conf = c;   }    + 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; + } +    mapping(string:mixed)|int(-1..0) first_try(RequestID id)   { -  +  if (id->misc->internal_get) return 0; +  +  mapping(string:mixed)|int(-1..0) res = check_if_header(id); +  if (res) { +  return res; +  } +     switch(id->method) {    case "OPTIONS":    return ([ "type":"text/html",    "data":"",    "extra_heads":([    "Allow":"CHMOD,COPY,DELETE,GET,HEAD,MKCOL,MKDIR,MOVE,"    "MV,PING,POST,PROPFIND,PROPPATCH,PUT,OPTIONS",    "Public":"CHMOD,COPY,DELETE,GET,HEAD,MKCOL,MKDIR,MOVE,"    "MV,PING,POST,PROPFIND,PROPPATCH,PUT,OPTIONS",    "Accept-Ranges":"bytes",
Roxen.git/server/modules/misc/webdav.pike:164:    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)];    if (zero_type(depth)) {    TRACE_LEAVE(sprintf("Bad depth header: %O.",    id->request_headers->depth));    return Roxen.http_status(400, "Unsupported depth.");    } +  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; +  }    } else if (id->request_headers->depth) {    // Depth header not supported in this case.    }      #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");
Roxen.git/server/modules/misc/webdav.pike:361:    "extra_heads":([ "Lock-Token":lock->locktoken ]),    ]);    case "UNLOCK":    string locktoken;    if (!(locktoken = id->request_headers["lock-token"])) {    TRACE_LEAVE("UNLOCK: No lock-token.");    return Roxen.http_status(400, "UNLOCK: Missing lock-token header.");    }    // The lock-token header is a Coded-URL.    sscanf(locktoken, "<%s>", locktoken); -  if (!objectp(lock = id->conf->check_locks(id->not_query, 0, id))) { +  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])) {    TRACE_LEAVE(sprintf("UNLOCK: Lock-token %O not found.", locktoken));    return Roxen.http_status(403, "UNLOCK: Lock not found.");    } -  if (lock->locktoken != locktoken) { -  SIMPLE_TRACE_LEAVE("UNLOCK: Locktoken mismatch: %O != %O.\n", -  locktoken, lock->locktoken); +  if (lock->path != path) { +  SIMPLE_TRACE_LEAVE("UNLOCK: Lock doesn't match path: %O != %O.\n", +  path, lock->path);    return Roxen.http_status(423, "Invalid locktoken.");    } -  mapping res = id->conf->unlock_file(id->not_query, lock, id); +  mapping res = id->conf->unlock_file(path, lock, id);    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.");       case "COPY":    case "MOVE":    if (!id->request_headers->destination) {
Roxen.git/server/modules/misc/webdav.pike:471:    recur_func = lambda(string source, string loc, int d, RoxenModule module,    RequestID id, string destination,    PropertyBehavior behavior,    Overwrite overwrite) {    if (!has_prefix(destination, loc)) {    // FIXME: Destination in other filesystem.    return 0;    }    // Convert destination to module location relative.    destination = destination[sizeof(loc)..]; -  mapping(string:mixed) res = -  ((id->method == "COPY")? -  module->recurse_copy_files: -  module->recurse_move_files) -  (source, destination, behavior, overwrite, id); +  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); +  }    if (res && ((res->error == 201) || (res->error == 204))) {    empty_result = res;    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. -  multiset(DAVLock) sub_locks = +  mapping(string:DAVLock) sub_locks =    module->find_locks(source, -1, 0, id); -  foreach(sub_locks||(<>);DAVLock lock;) { +  foreach(sub_locks||([]);;DAVLock lock) {    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.");    }    }
Roxen.git/server/modules/misc/webdav.pike:519:    };    empty_result = Roxen.http_status(404);    break;    case "DELETE":    recur_func = lambda(string path, string ignored, int d, RoxenModule module,    RequestID id) {    mapping res = module->recurse_delete_files(path, id);    if (res && res->error < 300) {    // Succeed in deleting some file(s).    empty_result = res; -  -  // RFC 4918 9.6: -  // A server processing a successful DELETE request: -  // -  // MUST destroy locks rooted on the deleted resource -  multiset(DAVLock) sub_locks = -  module->find_locks(path, -1, 0, id); -  foreach(sub_locks||(<>);DAVLock lock;) { -  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."); -  } -  } +     return 0;    }    return res;    };    // The multi status will be empty if everything went well,    // or if the file didn't exist.    empty_result = Roxen.http_status(404);    break;    case "PROPFIND": // Get meta data.    if (xml_data) {
Roxen.git/server/modules/misc/webdav.pike:703:    string href_prefix = combine_path(href, "./");    RoxenModule opaque_module;    foreach(conf->location_modules(), [string loc, function fun]) {    int d = depth;    string path; // Module location relative path.    SIMPLE_TRACE_ENTER(function_object(fun),    "Trying module mounted at %O...", 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)) { +  } else if (d && has_prefix(loc, href_prefix)) {    // loc = href_prefix + ... -  +  d -= sizeof((loc[sizeof(href_prefix)..])/"/"); +  if (d < 0) continue;    // && 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)) {