Roxen.git / server / base_server / module.pike

version» Context lines:

Roxen.git/server/base_server/module.pike:1:   // This file is part of Roxen WebServer.   // Copyright © 1996 - 2001, Roxen IS. - // $Id: module.pike,v 1.182 2004/05/07 17:34:14 grubba Exp $ + // $Id: module.pike,v 1.183 2004/05/07 19:44:58 mast Exp $      #include <module_constants.h>   #include <module.h>   #include <request_trace.h>      constant __pragma_save_parent__ = 1;      inherit "basic_defvar";   mapping(string:array(int)) error_log=([]);   
Roxen.git/server/base_server/module.pike:256:   string query_provides() { return 0; }         function(RequestID:int|mapping) query_seclevels()   {    if(catch(query("_seclevels")) || (query("_seclevels") == 0))    return 0;    return roxen.compile_security_pattern(query("_seclevels"),this_object());   }    + 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 + //! Path below the filesystem location to which the status applies. + //! + //! @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); + } +    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)   {    SIMPLE_TRACE_ENTER(this, "find_dir_stat(): %O", f);       array(string) files = find_dir(f, id);    mapping(string:Stat) res = ([]);       foreach(files || ({}), string fname) {
Roxen.git/server/base_server/module.pike:614:      #define LOOP_OVER_BOTH(PATH, LOCKS, CODE) \    do { \    foreach (file_locks; PATH; LOCKS) {CODE;} \    foreach (prefix_locks; PATH; LOCKS) {CODE;} \    } while (0)      //! Find some or all locks that apply to @[path].   //!   //! @param path - //! Path below the filesystem location. It's normalized with - //! @[VFS.normalize_path] and always ends with a @expr{"/"@}. + //! Normalized path below the filesystem location.   //!   //! @param recursive   //! If @expr{1@} also return locks anywhere below @[path].   //!   //! @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.)   //!
Roxen.git/server/base_server/module.pike:699:       TRACE_LEAVE(sprintf("Done, found %d locks.", sizeof(locks)));       return sizeof(locks) && locks;   }      //! Check if there are one or more locks that apply to @[path] for the   //! user the request is authenticated as.   //!   //! @param path - //! Path below the filesystem location. It's normalized with - //! @[VFS.normalize_path] and always ends with a @expr{"/"@}. + //! Normalized path below the filesystem location.   //!   //! @param recursive   //! If @expr{1@} also check recursively under @[path] for locks.   //!   //! @returns -  + //! The valid return values are:   //! @mixed   //! @type DAVLock - //! Returns 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 int(0..3) + //! 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   //! @int - //! @value 0 - //! Returns @expr{0@} if no locks apply. - //! @value 1 - //! Returns @expr{1@} if there only are one or more shared - //! locks held by other users. - //! @value 2 - //! Returns @expr{2@} if @[recursive] is set, the - //! authenticated user has locks under @[path] (but not on - //! @[path] itself), and there are no exclusive locks held - //! by other users. - //! @value 3 - //! Returns @expr{3@} if there are one or more exclusive - //! locks held by other users. + //! @value LOCK_NONE (0) + //! No locks apply. + //! @value LOCK_SHARED_BELOW (2) + //! There are only one or more shared locks held by other + //! users somewhere below @[path] (but not on @[path] + //! itself). Only returned if @[recursive] is set. + //! @value LOCK_SHARED_AT (3) + //! There are only one or more shared locks held by other + //! users on @[path]. + //! @value LOCK_OWN_BELOW (4) + //! The authenticated user has locks under @[path] (but not + //! on @[path] itself) and there are no exclusive locks held + //! by other users. Only returned if @[recursive] is set. + //! @value LOCK_EXCL_BELOW (6) + //! There are one or more exclusive locks held by other + //! users somewhere below @[path] (but not on @[path] + //! itself). Only returned if @[recursive] is set. + //! @value LOCK_EXCL_AT (7) + //! There are one or more exclusive locks held by other + //! users on @[path].   //! @endint -  + //! Note that the lowest bit is set for all flags that apply to + //! @[path] itself.   //! @endmixed   //!   //! @note   //! @[DAVLock] objects may be created if the filesystem has some   //! persistent storage of them. The default implementation does not   //! store locks persistently.   //!   //! @note   //! The default implementation only handles the @expr{"DAV:write"@}   //! lock type. - DAVLock|int(0..3) check_locks(string path, + DAVLock|LockFlag check_locks(string path,    int(0..1) recursive,    RequestID id)   {    // Common case.    if (!sizeof(file_locks) && !sizeof(prefix_locks)) return 0;       TRACE_ENTER(sprintf("check_locks(%O, %d, X)", path, recursive), this);       path = resource_id (path, id);       mixed auth_user = authenticated_user_id (path, id);       if (DAVLock lock =    file_locks[path] && file_locks[path][auth_user] ||    prefix_locks[path] && prefix_locks[path][auth_user]) {    TRACE_LEAVE(sprintf("Found lock %O.", lock->locktoken));    return lock;    } -  int(0..1) shared; +     -  +  LockFlag shared; +     if (mapping(mixed:DAVLock) locks = file_locks[path]) {    foreach(locks;; DAVLock lock) {    if (lock->lockscope == "DAV:exclusive") {    TRACE_LEAVE(sprintf("Found other user's exclusive lock %O.",    lock->locktoken)); -  return 3; +  return LOCK_EXCL_AT;    } -  shared = 1; +  shared = LOCK_SHARED_AT;    break;    }    }       foreach(prefix_locks;    string prefix; mapping(mixed:DAVLock) locks) {    if (has_prefix(path, prefix)) {    if (DAVLock lock = locks[auth_user]) return lock;    if (!shared)    // If we've found a shared lock then we won't find an    // exclusive one anywhere else.    foreach(locks;; DAVLock lock) {    if (lock->lockscope == "DAV:exclusive") {    TRACE_LEAVE(sprintf("Found other user's exclusive lock %O.",    lock->locktoken)); -  return 3; +  return LOCK_EXCL_AT;    } -  shared = 1; +  shared = LOCK_SHARED_AT;    break;    }    }    }       if (!recursive) {    TRACE_LEAVE(sprintf("Returning %O.", shared));    return shared;    }   
Roxen.git/server/base_server/module.pike:807:    // that apply to us.    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)); -  return 3; +  return LOCK_EXCL_BELOW;    } -  shared = 1; +  if (!shared) shared = LOCK_SHARED_BELOW;    break;    }    }    });    -  TRACE_LEAVE(sprintf("Returning %O.", locked_by_auth_user ? 2 : shared)); -  return locked_by_auth_user ? 2 : shared; +  TRACE_LEAVE(sprintf("Returning %O.", locked_by_auth_user ? LOCK_OWN_BELOW : shared)); +  return locked_by_auth_user ? LOCK_OWN_BELOW : shared;   }      //! Register @[lock] on the path @[path] under the assumption that   //! there is no other lock already that conflicts with this one, i.e.   //! that @code{check_locks(path,lock->recursive,id)@} would return - //! @expr{0@} if @expr{lock->lockscope@} is @expr{"DAV:exclusive"@}, or - //! @expr{0@} or @expr{1@} if @expr{lock->lockscope@} is - //! @expr{"DAV:shared"@}. + //! @expr{LOCK_NONE@} if @expr{lock->lockscope@} is + //! @expr{"DAV:exclusive"@}, or @expr{< LOCK_OWN_BELOW@} if + //! @expr{lock->lockscope@} is @expr{"DAV:shared"@}.   //!   //! This function is only provided as a helper to call from   //! @[lock_file] if the default lock implementation is to be used.   //!   //! @param path - //! Path below the filesystem location that the lock applies to. - //! It's normalized with @[VFS.normalize_path] and always ends with - //! a @expr{"/"@}. + //! Normalized path below the filesystem location that the lock + //! applies to.   //!   //! @param lock   //! The lock to register.   //!   //! @note   //! 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.   static void register_lock(string path, DAVLock lock, RequestID id)   {
Roxen.git/server/base_server/module.pike:867:    } else {    file_locks[path] = ([ auth_user:lock ]);    }    }    TRACE_LEAVE("Ok.");   }      //! Register @[lock] on the path @[path] under the assumption that   //! there is no other lock already that conflicts with this one, i.e.   //! that @code{check_locks(path,lock->recursive,id)@} would return - //! @expr{0@} if @expr{lock->lockscope@} is @expr{"DAV:exclusive"@}, or - //! @expr{0@} or @expr{1@} if @expr{lock->lockscope@} is - //! @expr{"DAV:shared"@}. + //! @expr{LOCK_NONE@} if @expr{lock->lockscope@} is + //! @expr{"DAV:exclusive"@}, or @expr{<= LOCK_SHARED_AT@} if + //! @expr{lock->lockscope@} is @expr{"DAV:shared"@}.   //!   //! 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 - //! lock on a file or directory prohibits other users from setting or - //! deleting any of its properties. An exclusive lock on a directory - //! prohibits other users from adding or removing files or directories - //! in it. A shared lock 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 RFC - //! 2518Bis (working draft)). + //! 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 + //! RFC 2518Bis (working draft)).   //!   //! 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 - //! Path below the filesystem location that the lock applies to. - //! It's normalized with @[VFS.normalize_path] and always ends with - //! a @expr{"/"@}. + //! Normalized path below the filesystem location that the lock + //! applies to.   //!   //! @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.   mapping(string:mixed) lock_file(string path,    DAVLock lock,    RequestID id)   {    return 0;   }      //! Remove @[lock] that currently is locking the resource at @[path].   //!   //! @param path - //! Path below the filesystem location that the lock applies to. - //! It's normalized with @[VFS.normalize_path] and always ends with - //! a @expr{"/"@}. + //! Normalized path below the filesystem location that the lock + //! applies to.   //!   //! @param lock   //! The lock to unregister. (It must not be changed or destructed.)   //!   //! @returns   //! Returns a status mapping on any error, zero otherwise.   mapping(string:mixed) unlock_file (string path,    DAVLock lock,    RequestID id)   { -  +  if (!sizeof (file_locks) && !sizeof (prefix_locks)) +  return 0; // Lock system not in use.    TRACE_ENTER(sprintf("unlock_file(%O, lock(%O), X).", path, lock->locktoken),    this);    mixed auth_user = authenticated_user_id (path, id);    path = resource_id (path, id);    DAVLock removed_lock;    if (lock->recursive) { -  if (prefix_locks[path]) { +     removed_lock = m_delete(prefix_locks[path], auth_user); -  if (!sizeof(prefix_locks[path])) m_delete(prefix_locks, path); +  if (!sizeof (prefix_locks[path])) m_delete (prefix_locks, path);    } -  } +     else if (file_locks[path]) {    removed_lock = m_delete (file_locks[path], auth_user);    if (!sizeof (file_locks[path])) m_delete (file_locks, path);    } -  ASSERT_IF_DEBUG (!removed_lock || lock /*%O*/ == removed_lock /*%O*/, -  lock, removed_lock); +  ASSERT_IF_DEBUG (lock /*%O*/ == removed_lock /*%O*/, lock, removed_lock);    TRACE_LEAVE("Ok.");    return 0;   }      //! Check if we may perform a write access to @[path].   //!   //! The default implementation checks if the current locks match the   //! if-header.   //! - //! Usually called from @[find_file()]. + //! Usually called from @[find_file()], @[delete_file()] or similar.   //!   //! @note   //! Does not support checking against etags yet.   //!   //! @param path   //! Path below the filesystem location that the lock applies to. - //! It's normalized with @[VFS.normalize_path]. +    //!   //! @param recursive   //! If @expr{1@} also check write access recursively under @[path].   //!   //! @returns - //! Returns @expr{0@} (zero) on success and - //! a result mapping on failure. - mapping(string:mixed) write_access(string path, int(0..1) recursive, RequestID id) + //! 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 not somewhere recursively. The caller + //! should in the last case check recursively with @[write_access] + //! for each member in the directory. + mapping(string:mixed)|int(0..1) write_access(string path, int(0..1) recursive, +  RequestID id)   { -  // FIXME: Implement recursive! -  -  if (!has_suffix (path, "/")) path += "/"; -  +     SIMPLE_TRACE_ENTER(this, "write_access(%O, %O, X)", path, recursive);    -  int(0..3)|DAVLock lock = check_locks(path, 0, id); +  int/*LockFlag*/|DAVLock lock = check_locks(path, recursive, id);    -  if (lock && intp(lock)) { +  int(0..1) got_sublocks; +  if (intp(lock) && !(<LOCK_NONE, LOCK_OWN_BELOW>)[lock]) { +  if (lock & 1) {    TRACE_LEAVE("Locked by other user.");    return Roxen.http_status(Protocols.HTTP.DAV_LOCKED);    } -  +  else +  got_sublocks = 1; +  }    -  +  if (!has_suffix (path, "/")) path += "/"; // get_if_data always adds a "/".    path = query_location() + path; // No need for fancy combine_path stuff here.       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])) {    if (lock) {    TRACE_LEAVE("Locked, no if header.");    return Roxen.http_status(Protocols.HTTP.DAV_LOCKED);    }    TRACE_LEAVE("No lock and no if header."); -  return 0; // No condition and no lock -- Ok. +  return got_sublocks; // No condition and no lock -- Ok.    } -  +     mapping(string:mixed) res;    next_condition:    foreach(condition, array(array(string)) sub_cond) {    SIMPLE_TRACE_ENTER(this,    "Trying condition ( %{%{%x:%O%} %})...", sub_cond);    int negate;    foreach(sub_cond, array(string) token) {    switch(token[0]) {    case "not":    negate = !negate;
Roxen.git/server/base_server/module.pike:1020:    // Lock mismatch.    TRACE_LEAVE("Lock mismatch.");    continue next_condition; // Fail.    }    negate = 0;    break;    }    }    TRACE_LEAVE("Found match.");    TRACE_LEAVE("Ok."); -  return 0; // Found matching sub-condition. +  return got_sublocks; // Found matching sub-condition.    } -  +     TRACE_LEAVE("Failed.");    return res || Roxen.http_status(Protocols.HTTP.HTTP_PRECOND_FAILED);   }      mapping(string:mixed)|int(-1..0)|Stdio.File find_file(string path,    RequestID id);      //! Delete the file specified by @[path].   //!   //! @note