Branch: Tag:

2018-03-28

2018-03-28 10:47:14 by Henrik Grubbström (Grubba) <grubba@grubba.org>

Merge branch 'patches/ws168' into devel

* patches/ws168:
Filesystem: Removed some redundant code.
Filesystem [WebDAV]: Multiple write lock checks adjusted.
REQUEST_TRACE: Some adjusted traces.
WebDAV: Moved responsibility for implicit DELETE unlock.
RoxenModule: Improved support for MultiStatus in move_collection().
MultiStatus: Added one more variant of add_status() et al.
Configuration [WebDAV]: Changed API for check_locks().
Configuration [WebDAV]: Use find_locks() in lock_file().
RequestID: Added variants of set_status_for_{path,url}().
DAVLock: Added field is_file.
WebDAV: Move main checking of the if-header to the webdav module.
Configuration [WebDAV]: Added query_property().
WebDAV: Changed API for find_locks().
HTTP [DAV]: Improved multi status detection.
HTTP [DAV]: Adjust the if-header to always be in tagged-list syntax.
RequestID: Fixed parsing of if-header.

1756:    break;       case 200: +  // NB: Note the setting of extra_heads above. +  if (sizeof(m) <= 1) { +  res = "Returned multi status. "; +  break; +  }    res = "Returned ok. ";    break;   
1789:   }      //! Find all applicable locks for this user on @[path]. - multiset(DAVLock) find_locks(string path, int(-1..1) recursive, + mapping(string:DAVLock) find_locks(string path, int(-1..1) recursive,    int(0..1) exclude_shared, RequestID id)   {    SIMPLE_TRACE_ENTER(0, "find_locks(%O, %O, %O, X)",    path, recursive, exclude_shared); -  multiset(DAVLock) locks = (<>); +  mapping(string:DAVLock) locks = ([]);       foreach(location_module_cache||location_modules(),    [string loc, function func])
1815:    }    TRACE_ENTER(sprintf("subpath: %O", subpath),    function_object(func)->find_locks); -  multiset(DAVLock) sub_locks = +  mapping(string:DAVLock) sub_locks =    function_object(func)->find_locks(subpath, recursive,    exclude_shared, id);    TRACE_LEAVE("");
1830:    return locks;   }    - //! Check if there are any applicable locks for this user on @[path]. - DAVLock|LockFlag check_locks(string path, int(0..1) recursive, RequestID id) + //! Check that all locks that apply to @[path] for the user the request + //! is authenticated as have been mentioned in the if-header. + //! + //! WARNING: This function has some design issues and will very likely + //! get a different interface. Compatibility is NOT guaranteed. + //! + //! @param path + //! Normalized path below the filesystem location. + //! + //! @param recursive + //! If @expr{1@} also check recursively under @[path] for locks. + //! + //! @returns + //! Returns one of + //! @mixed + //! @type int(0..0) + //! Zero if not locked, or all locks were mentioned. + //! @type mapping(zero:zero) + //! An empty mapping if @[recursive] was true and there + //! were unmentioned locks on paths with @[path] as a prefix. + //! The missing locks are registered in the multistatus for + //! the @[id] object. + //! @type mapping(string:mixed) + //! A @[Protocols.HTTP.DAV_LOCKED] error status in all other cases. + //! @endmixed + //! + //! @note + //! @[DAVLock] objects may be created if the filesystem has some + //! persistent storage of them. The default implementation does not + //! store locks persistently. + mapping(string:mixed)|int(-1..0) check_locks(string path, +  int(0..1) recursive, +  RequestID id)   { -  LockFlag state = 0; -  foreach(location_module_cache||location_modules(), -  [string loc, function func]) -  { -  string subpath; -  int check_above; -  if (has_prefix(path, loc)) { -  // path == loc + subpath. -  subpath = path[sizeof(loc)..]; -  } else if (recursive && has_prefix(loc, path)) { -  // loc == path + ignored. -  subpath = ""; -  check_above = 1; -  } else { -  // Does not apply to this location module. -  continue; +  TRACE_ENTER(sprintf("check_locks(%O, %d, X)", path, recursive), this); +  +  mapping(string:DAVLock) locks = find_locks(path, recursive, 0, id); +  // Common case. +  if (!sizeof(locks)) { +  TRACE_LEAVE ("Got no locks."); +  return 0;    } -  int/*LockFlag*/|DAVLock lock_info = -  function_object(func)->check_locks(subpath, recursive, id); -  if (objectp(lock_info)) { -  if (!check_above) { -  return lock_info; -  } else { -  lock_info = LOCK_OWN_BELOW; // We have a lock on some subpath. +  +  mapping(string:array(array(array(string)))) if_data = id->get_if_data(); +  if (if_data) { +  foreach(if_data[0], array(array(string)) tokens) { +  m_delete(locks, tokens[0][1]);    } -  +  +  if (!sizeof(locks)) { +  TRACE_LEAVE ("All locks unlocked."); +  return 0;    } -  else -  if (check_above && (lock_info & 1)) -  // Convert LOCK_*_AT to LOCK_*_BELOW. -  lock_info &= ~1; -  if (lock_info > state) state = lock_info; -  if (state == LOCK_EXCL_AT) return LOCK_EXCL_AT; // Doesn't get any worse. -  if (function_object(func)->webdav_opaque) break; +     } -  return state; +  +  // path = id->not_query; +  if (!has_suffix(path, "/")) path += "/"; +  mapping(string:mixed) ret = +  Roxen.http_dav_error(Protocols.HTTP.DAV_LOCKED, "lock-token-submitted"); +  foreach(locks;;DAVLock lock) { +  TRACE_ENTER(sprintf("Checking lock %O against %O.", lock, path), 0); +  if (has_prefix(path, lock->path)) { +  TRACE_LEAVE("Direct lock."); +  TRACE_LEAVE("Locked."); +  return ret;    } -  +  if (lock->is_file) { +  id->set_status_for_path(lock->path[..<1], ret); +  } else { +  id->set_status_for_path(lock->path, ret); +  } +  TRACE_LEAVE("Added to multi status."); +  } +  TRACE_LEAVE("Multi status."); +  return ([]); + }      protected multiset(DAVLock) active_locks = (<>);   
1980:    array(Parser.XML.Tree.Node) owner,    RequestID id)   { +  TRACE_ENTER(sprintf("%O(%O, %O, %O, %O, %O, %O, %O)", +  this_function, path, recursive, lockscope, +  locktype, expiry_delta, owner, id), 0); +  +  int is_file; +     // Canonicalize path. -  if (!has_suffix(path, "/")) path+="/"; +  if (!has_suffix(path, "/")) { +  path+="/"; +  is_file = 1; +  }    -  // First check if there's already some lock on path that prevents +  // FIXME: Race conditions! +  +  int fail; +  +  // First check if there's already some lock on the path that prevents    // us from locking it. -  int/*LockFlag*/|DAVLock lock_info = check_locks(path, recursive, id); +  mapping(string:DAVLock) locks = find_locks(path, recursive, 0, id);    -  if (!intp(lock_info)) { -  // We already hold a lock that prevents us. -  if (id->request_headers->if) { -  return Roxen.http_status(412, "Precondition Failed"); -  } else { -  return Roxen.http_status(423, "Locked"); +  foreach(locks; string lock_token; DAVLock lock) { +  TRACE_ENTER(sprintf("Checking lock %O...\n", lock), 0); +  if ((lock->lockscope == "DAV:exclusive") || +  (lockscope == "DAV:exclusive")) { +  TRACE_LEAVE("Locked."); +  id->set_status_for_path(lock->path, 423, "Locked"); +  fail = 1;    } -  } else if (lockscope == "DAV:exclusive" ? -  lock_info >= LOCK_SHARED_BELOW : -  lock_info >= LOCK_OWN_BELOW) { -  // Some other lock prevents us. -  return Roxen.http_status(423, "Locked"); +  TRACE_LEAVE("Shared.");    }    -  +  if (fail) { +  TRACE_LEAVE("Fail."); +  return ([]); +  } +     // Create the new lock.       string locktoken = "urn:uuid:" + roxen->new_uuid_string();    DAVLock lock = DAVLock(locktoken, path, recursive, lockscope, locktype,    expiry_delta, owner); -  +  lock->is_file = is_file;    foreach(location_module_cache||location_modules(),    [string loc, function func])    {
2021:    continue;    }    +  TRACE_ENTER(sprintf("Calling %O->lock_file(%O, %O, %O)...", +  function_object(func), subpath, lock, id), 0);    mapping(string:mixed) lock_error =    function_object(func)->lock_file(subpath, lock, id);    if (lock_error) {
2041:    if (func == func2) break;    }    // destruct(lock); +  TRACE_LEAVE(sprintf("Lock error: %O", lock_error));    return lock_error;    } -  +  TRACE_LEAVE("Ok.");    if (function_object(func)->webdav_opaque) break;    }   
2059:    }       // Success. +  TRACE_LEAVE("Success.");    return lock;   }    -  + //! Returns the value of the specified property, or an error code + //! mapping. + //! + //! @note + //! Returning a string is shorthand for returning an array + //! with a single text node. + //! + //! @seealso + //! @[query_property_set()] + string|array(Parser.XML.Tree.SimpleNode)|mapping(string:mixed) +  query_property(string path, string prop_name, RequestID id) + { +  foreach(location_module_cache||location_modules(), +  [string loc, function func]) +  { +  if (!has_prefix(path, loc)) { +  // Does not apply to this location module. +  continue; +  } +  +  // path == loc + subpath. +  string subpath = path[sizeof(loc)..]; +  +  string|array(Parser.XML.Tree.SimpleNode)|mapping(string:mixed) res = +  function_object(func)->query_property(subpath, prop_name, id); +  if (mappingp(res) && (res->error == 404)) { +  // Not found in this module; try the next. +  continue; +  } +  return res; +  } +  return Roxen.http_status(Protocols.HTTP.HTTP_NOT_FOUND, "No such property."); + } +    mapping|int(-1..0) low_get_file(RequestID id, int|void no_magic)   //! The function that actually tries to find the data requested. All   //! modules except last and filter type modules are mapped, in order,
2647: Inside #if defined(URL_MODULES)
   TRACE_LEAVE("");    if(err)    throw(err); +     if (arrayp(dir)) {    return map(dir, combine_combiners);    }