Roxen.git
/
server
/
base_server
/
module.pike
version
»
Context lines:
10
20
40
80
file
none
3
Roxen.git/server/base_server/module.pike:552:
string ext; if(stringp(sub_id->extension)) { sub_id->not_query += sub_id->extension; ext = lower_case(Roxen.extension(sub_id->not_query, sub_id)); } array(string) tmp=sub_id->conf->type_from_filename(sub_id->not_query, 1, ext); if(tmp) res = ([ "file":res, "type":tmp[0], "encoding":tmp[1] ]); else res = (["file": res]);
+
} else if (!res) {
+
res = Roxen.http_status(404, "File not found.");
} response_headers = sub_id->make_response_headers (res); destruct (sub_id); } return response_headers; } } //! Return the set of properties for @[path].
Roxen.git/server/base_server/module.pike:890:
//! any above @[path]. (This is appropriate to use to get the //! list of locks that need to be unlocked on DELETE.) //! //! @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.) //! //! @returns
-
//! Returns a
multiset
containing all applicable locks in
+
//! Returns a
mapping
containing all applicable locks in
//! this location module, or @expr{0@} (zero) if there are none. //! //! @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.
-
multiset
(DAVLock) find_locks(string path,
+
mapping
(
string:
DAVLock) find_locks(string path,
int(-1..1) recursive, int(0..1) exclude_shared, RequestID id) { // Common case. if (!sizeof(file_locks) && !sizeof(prefix_locks)) return 0; TRACE_ENTER(sprintf("find_locks(%O, %O, %O, X)", path, recursive, exclude_shared), this); string rsc = resource_id (path, id);
-
multiset
(DAVLock) locks = (
<>
);
+
mapping
(
string:
DAVLock) locks = (
[]
);
function(mapping(mixed:DAVLock):void) add_locks; if (exclude_shared) { mixed auth_user = authenticated_user_id (path, id); add_locks = lambda (mapping(mixed:DAVLock) sub_locks) { foreach (sub_locks; string user; DAVLock lock) if (user == auth_user || lock->lockscope == "DAV:exclusive")
-
locks[lock] =
1
;
+
locks[lock
->locktoken
] =
lock
;
}; } else add_locks = lambda (mapping(mixed:DAVLock) sub_locks) {
-
locks |=
mkmultiset
(values (sub_locks));
+
locks |=
mkmapping
(values
(sub_locks)->locktoken,
+
values
(sub_locks));
}; if (file_locks[rsc]) { add_locks (file_locks[rsc]); } if (recursive >= 0) { foreach(prefix_locks; string prefix; mapping(mixed:DAVLock) sub_locks) { if (has_prefix(rsc, prefix)) {
Roxen.git/server/base_server/module.pike:960:
}); } add_locks = 0; 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
.
+
//!
Unlock
any
locks
pertaining
to @[path]
or
to anything
+
//!
under
@[path]
.
//!
-
//!
WARNING:
This function
has
some
design
issues
and
will
very
likely
-
//!
get
a
different
interface
.
Compatibility is NOT guaranteed.
+
//! This function
is
used
to
unlock
locks
after
files
or
+
//!
directories
have
been
deleted
.
//!
-
//!
@param
path
-
//!
Normalized
path below
the
filesystem location
.
+
//!
The
default
implementation
just
unregisters
the
locks
.
//!
-
//!
@param recursive
-
//!
If @expr{1@} also check recursively under
@[
path] for locks.
-
//!
-
//! @returns
-
//! The valid return values are:
-
//! @mixed
-
//! @type DAVLock
-
//! 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 LOCK_NONE
-
//! No locks apply. (0)
-
//! @value LOCK_SHARED_BELOW
-
//! 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. (2)
-
//! @value LOCK
_
SHARED_AT
-
//! There are only one or more shared locks held by other
-
//! users on @[
path
].
(
3)
-
//! @value LOCK_OWN_BELOW
-
//! 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. (4)
-
//! @value LOCK_EXCL_BELOW
-
//! 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. (6)
-
//! @value LOCK_EXCL_AT
-
//! There are one or more exclusive locks held by other
-
//! users on @[path]. (7)
-
//! @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|LockFlag check_locks(
string path,
-
int(0..1) recursive,
-
RequestID id)
+
//!
@seealso
+
//! @[
unregister_
lock()]
+
void
unlock
_path(string path, RequestID id)
{
-
TRACE_ENTER(sprintf("check_locks(%O, %d, X)", path, recursive), this);
-
+
// Common case.
-
if (!sizeof(file_locks) && !sizeof(prefix_locks))
{
-
TRACE_LEAVE ("Got no locks");
-
return 0;
-
}
+
if (!sizeof(file_locks) && !sizeof(prefix_locks)) return 0;
-
mixed auth
_
user = authenticated_user_id
(path,
id
)
;
-
path
= resource_
id
(path
,
id
);
+
TRACE
_
ENTER(sprintf("unlock
_
path
(
%O
,
%O
)
",
path
,
id
)
,
this
);
-
if
(DAVLock
lock
=
-
file
_
locks[path] && file_locks[path][auth_user] ||
-
prefix_locks[path] && prefix_locks[path][auth_user]) {
-
TRACE_LEAVE
(
sprintf("Found own lock %O."
,
lock->locktoken
)
)
;
-
return lock;
-
}
+
string
rsc
=
resource
_
id(
path,
id
);
-
LockFlag
shared;
+
//
NB: The following code leaves dead locks in conf->active_locks!
-
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
LOCK
_
EXCL_AT
;
+
foreach(file_locks;
string prefix;
mapping(mixed:DAVLock)
sub
_locks) {
+
if (
has_prefix(prefix,
rsc
)
)
{
+
TRACE_
ENTER
(sprintf("
Unlocking
%d
locks
for
path
%O.
..
",
+
sizeof(sub_locks),
prefix),
this
);
+
m_delete(file_locks,
prefix);
+
TRACE
_
LEAVE("")
;
}
-
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])
{
-
SIMPLE_TRACE_LEAVE
("Found
own
lock
%O
on
%O.",
lock->locktoken
, prefix);
-
return
lock
;
+
foreach(prefix_locks; string prefix; mapping(mixed:DAVLock)
sub_
locks) {
+
if (has_prefix(
prefix
,
rsc
)) {
+
TRACE_ENTER
(
sprintf("Unlocking
%d
locks
for path %O...",
+
sizeof(sub_locks)
, prefix)
, this)
;
+
m_delete(prefix_locks,
prefix);
+
TRACE_LEAVE("")
;
}
-
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 LOCK_EXCL_AT;
+
}
-
shared = LOCK_SHARED_AT;
-
break;
-
}
-
}
-
}
+
-
if (!recursive) {
-
SIMPLE_
TRACE_LEAVE("
Returning %O
."
, shared
);
-
return shared;
+
TRACE_LEAVE("
Done
.");
}
-
int(0..1) locked_by_auth_user;
-
-
// We want to know if there are any locks with @[path] as prefix
-
// 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 LOCK_EXCL_BELOW;
-
}
-
if (!shared) shared = LOCK_SHARED_BELOW;
-
break;
-
}
-
}
-
});
-
-
SIMPLE_TRACE_LEAVE("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 @expr{check_locks(path,lock->recursive,id)@} would return
-
//! @expr{LOCK_NONE@} if @expr{lock->lockscope@} is
-
//! @expr{"DAV:exclusive"@}, or @expr{< LOCK_OWN_BELOW@} if
-
//! @expr{lock->lockscope@} is @expr{"DAV:shared"@}.
+
//! there is no other lock already that conflicts with this one.
//! //! This function is only provided as a helper to call from //! @[lock_file] if the default lock implementation is to be used. //! //! @param path //! Normalized path (below the filesystem location) that the lock //! applies to. //! //! @param lock //! The lock to register.
Roxen.git/server/base_server/module.pike:1181:
if (lock->recursive) { if (id) { removed_lock = m_delete(prefix_locks[path], auth_user); } else { foreach(prefix_locks[path]||([]); mixed user; DAVLock l) { if (l == lock) { removed_lock = m_delete(prefix_locks[path], user); } } }
-
if (!sizeof (prefix_locks[path])) m_delete
(prefix_locks, path);
+
if (
prefix_locks[path] &&
!sizeof (prefix_locks[path]))
{
+
m_delete(prefix_locks, path);
}
-
+
}
else if (file_locks[path]) { if (id) { removed_lock = m_delete (file_locks[path], auth_user); } else { foreach(file_locks[path]||([]); mixed user; DAVLock l) { if (l == lock) { removed_lock = m_delete(file_locks[path], user); } } }
Roxen.git/server/base_server/module.pike:1204:
} // NB: The lock may have already been removed in the !id case. ASSERT_IF_DEBUG (!(id || removed_lock) || (lock /*%O*/ == removed_lock /*%O*/), lock, removed_lock); TRACE_LEAVE("Ok."); return 0; } //! Register @[lock] on the path @[path] under the assumption that
-
//! there is no other lock already that conflicts with this one
, i
.
e.
-
//! that @expr{check_locks(path,lock->recursive,id)@} would return
-
//! @expr{LOCK_NONE@} if @expr{lock->lockscope@} is
-
//! @expr{"DAV:exclusive"@}, or @expr{<= LOCK_SHARED_AT@} if
-
//! @expr{lock->lockscope@} is @expr{"DAV:shared"@}.
+
//! there is no other lock already that conflicts with this one.
//! //! 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 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
Roxen.git/server/base_server/module.pike:1284:
//! @returns //! Returns a status mapping on any error, zero otherwise. //! //! @note //! To use the default lock implementation, call @[unregister_lock] //! from this function. mapping(string:mixed) unlock_file (string path, DAVLock lock, RequestID|int(0..0) id);
-
//! Checks that the conditions specified by the WebDAV @expr{"If"@}
-
//! header are fulfilled on the given path (RFC 2518 9.4). This means
-
//! that locks are checked as necessary using @[check_locks].
-
//!
-
//! WARNING: This function has some design issues and will very likely
-
//! get a different interface. Compatibility is NOT guaranteed.
-
//!
-
//! @param path
-
//! Path (below the filesystem location) that the lock applies to.
-
//!
-
//! @param recursive
-
//! If @expr{1@} also check write access recursively under @[path].
-
//!
-
//! @returns
-
//! 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 maybe not somewhere below. The caller
-
//! should in the last case do the operation on this level if
-
//! possible and then handle each member in the directory
-
//! recursively with @[write_access] etc.
-
mapping(string:mixed)|int(0..1) check_if_header(string relative_path,
-
int(0..1) recursive,
-
RequestID id)
-
{
-
SIMPLE_TRACE_ENTER(this, "Checking \"If\" header for %O",
-
relative_path);
-
-
int/*LockFlag*/|DAVLock lock = check_locks(relative_path, recursive, id);
-
-
int(0..1) got_sublocks;
-
if (lock && intp(lock)) {
-
if (lock & 1) {
-
TRACE_LEAVE("Locked by other user.");
-
return Roxen.http_status(Protocols.HTTP.DAV_LOCKED);
-
}
-
else if (recursive)
-
// This is set for LOCK_OWN_BELOW too since it might be
-
// necessary to come back here and check the If header for
-
// those locks.
-
got_sublocks = 1;
-
}
-
-
string path = relative_path;
-
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);
-
}
-
SIMPLE_TRACE_LEAVE("No lock and no if header - ok%s.",
-
got_sublocks ? " (this level only)" : "");
-
return got_sublocks; // No condition and no lock -- Ok.
-
}
-
-
string|int(-1..0) etag;
-
-
int(0..1) locked_fail = !!lock;
-
next_condition:
-
foreach(condition, array(array(string)) sub_cond) {
-
SIMPLE_TRACE_ENTER(this,
-
"Trying condition ( %{%s:%O %})...", sub_cond);
-
int negate;
-
DAVLock locked = lock;
-
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 = query_property(relative_path,
-
"DAV:getetag", id))) {
-
etag = -1;
-
}
-
}
-
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":
-
// The user has specified a key, so don't fail with DAV_LOCKED.
-
locked_fail = 0;
-
if (negate) {
-
if (lock && lock->locktoken == token[1]) {
-
TRACE_LEAVE("Matched negated lock.");
-
continue next_condition; // Fail.
-
}
-
} else if (!lock || lock->locktoken != token[1]) {
-
// Lock mismatch.
-
TRACE_LEAVE("Lock mismatch.");
-
continue next_condition; // Fail.
-
} else {
-
locked = 0;
-
}
-
negate = 0;
-
break;
-
}
-
}
-
if (!locked) {
-
TRACE_LEAVE("Found match.");
-
SIMPLE_TRACE_LEAVE("Ok%s.",
-
got_sublocks ? " (this level only)" : "");
-
return got_sublocks; // Found matching sub-condition.
-
}
-
SIMPLE_TRACE_LEAVE("Conditional ok, but still locked (locktoken: %O).",
-
lock->locktoken);
-
locked_fail = 1;
-
}
-
-
if (locked_fail) {
-
TRACE_LEAVE("Failed (locked).");
-
} else {
-
TRACE_LEAVE("Precondition failed.");
-
}
-
return Roxen.http_status(locked_fail ?
-
Protocols.HTTP.DAV_LOCKED :
-
Protocols.HTTP.HTTP_PRECOND_FAILED);
-
}
-
+
//! Used by some default implementations to check if we may perform a //! write access to @[path]. It should at least call
-
//! @[check_
if_header
] to check DAV locks. It takes the
same arguments
-
//! and has the same return value as that function.
+
//! @[
Configuration::
check_
locks()
] to check DAV locks. It takes the
+
//!
same arguments
and has the same return value as that function.
//! //! WARNING: This function has some design issues and will very likely //! get a different interface. Compatibility is NOT guaranteed. //! //! A filesystem module should typically put all needed write access //! checks here and then use this from @[find_file()], //! @[delete_file()] etc. //! //! @returns //! 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 maybe not somewhere below. The caller //! should in the last case do the operation on this level if //! possible and then handle each member in the directory //! recursively with @[write_access] etc. protected mapping(string:mixed)|int(0..1) write_access(string relative_path, int(0..1) recursive, RequestID id) {
-
return
check_if_header
(relative_path, recursive, id);
+
string
path = query
_
location()
+
relative_path
;
+
return id->conf->check_locks(path
, recursive, id);
} //! protected variant mapping(string:mixed)|int(0..1) write_access(array(string) paths, int(0..1) recursive, RequestID id) { mapping(string:mixed)|int(0..1) ret;
-
int got_ok;
+
foreach(paths, string path) { ret = write_access(path, recursive, id);
-
if (
!ret) {
-
got_ok = 1;
-
continue;
-
}
-
if
(ret
== 1
)
{
-
continue;
-
}
-
if (ret->error == Protocols.HTTP.HTTP_PRECOND_FAILED
) {
-
continue;
-
}
+
if (
mappingp
(ret)) {
return ret; }
-
+
}
-
if (got_ok) {
-
//
The
if headers are valid for at least one
of the paths
,
-
// and none of the other paths
are locked.
+
//
None
of the paths are locked.
return 0; }
-
// HTTP_PRECOND_FAILED for all of the paths.
-
return ret;
-
}
-
+
mapping(string:mixed)|int(-1..0)|Stdio.File find_file(string path, RequestID id); //! Used by the default @[recurse_delete_files] implementation to //! delete a file or an empty directory. //! //! @returns //! Returns a 2xx series status mapping on success (typically 204 No //! Content). Returns 0 if the file doesn't exist. Returns an //! appropriate status mapping for any other error.
Roxen.git/server/base_server/module.pike:1527:
if (!stat) stat = id->get_multi_status()->prefix (id->url_base() + query_location()[1..]); Stat st = stat_file(path, id); if (!st) { SIMPLE_TRACE_LEAVE ("No such file or directory"); return 0; }
+
mapping(string:mixed) ret = write_access(path, 1, id);
+
if (ret) {
+
SIMPLE_TRACE_LEAVE("Write access denied: %O", ret);
+
return ret;
+
}
+
mapping(string:mixed) recurse (string path, Stat st) { // Note: Already got an extra TRACE_ENTER level on entry here. if (st->isdir) { // RFC 2518 8.6.2 // The DELETE operation on a collection MUST act as if a // "Depth: infinity" header was used on it. int fail; if (!has_suffix(path, "/")) path += "/"; foreach(find_dir(path, id) || ({}), string fname) { fname = path + fname; if (Stat sub_stat = stat_file (fname, id)) { SIMPLE_TRACE_ENTER (this, "Deleting %O", fname); if (mapping(string:mixed) sub_res = recurse(fname, sub_stat)) { // RFC 2518 8.6.2 // Additionally 204 (No Content) errors SHOULD NOT be returned // in the 207 (Multi-Status). The reason for this prohibition // is that 204 (No Content) is the default success code. if (sizeof (sub_res) && sub_res->error != 204) {
-
stat->add_status(fname, sub_res
->error, sub_res->rettext
);
+
stat->add_status(fname, sub_res);
} if (!sizeof (sub_res) || sub_res->error >= 300) fail = 1; } } } if (fail) { SIMPLE_TRACE_LEAVE ("Partial failure"); return ([]); } }
Roxen.git/server/base_server/module.pike:1720:
// // 412 (Precondition Failed) - /.../ the Overwrite header // is "F" and the state of the destination resource is // non-null. // // That clearly doesn't include this case. Also, common sense // says that the error from the failed delete is more useful // to the client. #if 0 return Roxen.http_status(Protocols.HTTP.HTTP_PRECOND_FAILED);
-
#
else
+
#
elif 0
if (sizeof (res)) { // RFC 2518 8.8.3: // If an error in executing the COPY method occurs with a // resource other than the resource identified in the // Request-URI then the response MUST be a 207 // (Multi-Status). // // So if the failure was on the root destination resource we // have to convert it to a multi-status. result->add_status (destination, res->error, res->rettext); } return ([]);
-
+
#else
+
// RFC 4918 9.8.5:
+
// 423 (Locked) - The destination resource, or resource
+
// within the destination collection, was locked. This
+
// response SHOULD contain the 'lock-token-submitted'
+
// precondition element.
+
return res;
#endif } TRACE_LEAVE("Deletion ok."); break; case NEVER_OVERWRITE: TRACE_LEAVE("Destination already exists."); return Roxen.http_status(Protocols.HTTP.HTTP_PRECOND_FAILED); case MAYBE_OVERWRITE: // No overwrite header. // Be nice, and fail only if we don't already have a collection. if (st->isdir) { TRACE_LEAVE("Destination exists and is a directory."); return copy_properties(source, destination, behavior, id); } TRACE_LEAVE("Destination exists and is not a directory.");
-
return Roxen.http_status(Protocols.HTTP.HTTP_
PRECOND_FAILED
);
+
return Roxen.http_status(Protocols.HTTP.HTTP_
CONFLICT
);
} } // Create the new collection. TRACE_LEAVE("Make a new collection."); mapping(string:mixed) res = make_collection(destination, id); if (res && res->error >= 300) return res;
-
return
copy_properties(source, destination, behavior, id) || res;
+
res
=
copy_properties(source, destination, behavior, id) || res;
+
if (res && st && (res->error == Protocols.HTTP.HTTP_CREATED)) {
+
// RFC 4918 9.8.5:
+
// 204 (No Content) - The source resource was successfully
+
// copied to a preexisting destination resource.
+
res->error = Protocols.HTTP.HTTP_NO_CONTENT;
}
-
+
return res;
+
}
//! Used by the default @[recurse_copy_files] to copy a single file //! along with its properties. //! //! @param source //! Source path below the filesystem location. //! //! @param destination //! Destination path below the filesystem location. //!
Roxen.git/server/base_server/module.pike:1806:
//! Destination path below the filesystem location. //! //! @param behavior //! Specifies how to copy properties. See the @[PropertyBehavior] //! type for details. //! //! @param overwrite //! Specifies how to handle the situation if the destination already //! exists. See the @[Overwrite] type for details. //!
+
//! @param one_level
+
//! Indicates whether recursion is to be inhibited.
+
//!
//! @returns //! Returns a 2xx series status mapping on success (typically 201 //! Created if the destination didn't exist before, or 204 No //! Content otherwise). Returns 0 if the source doesn't exist. //! Returns an appropriate status mapping for any other error. That //! includes an empty mapping in case there's a failure on some //! subpart or at the destination, to signify a 207 Multi-Status //! response using the info in @[id->get_multi_status()]. mapping(string:mixed) recurse_copy_files(string source, string destination, PropertyBehavior behavior,
-
Overwrite overwrite, RequestID id)
+
Overwrite overwrite, RequestID id
,
+
int|void one_level
)
{ SIMPLE_TRACE_ENTER(this, "Recursive copy from %O to %O (%s)", source, destination, overwrite == DO_OVERWRITE ? "replace" : overwrite == NEVER_OVERWRITE ? "no overwrite" : "overlay"); string src_tmp = has_suffix(source, "/")?source:(source+"/"); string dst_tmp = has_suffix(destination, "/")?destination:(destination+"/"); if ((src_tmp == dst_tmp) || has_prefix(src_tmp, dst_tmp) || has_prefix(dst_tmp, src_tmp)) { TRACE_LEAVE("Source and destination overlap."); return Roxen.http_status(403, "Source and destination overlap."); } string prefix = map(query_location()[1..]/"/", Roxen.http_encode_url)*"/"; MultiStatus.Prefixed result = id->get_multi_status()->prefix (id->url_base() + prefix);
-
mapping(string:mixed) recurse(string source, string destination) {
+
mapping(string:mixed) recurse(string source, string destination
,
+
int|void one_level
) {
// Note: Already got an extra TRACE_ENTER level on entry here. Stat st = stat_file(source, id); if (!st) { TRACE_LEAVE("Source not found."); return 0; }
-
//
FIXME
:
Check
destination
?
+
//
NB
:
No
need to check the
destination
here, as it is done by
+
// copy_collection() and copy_file().
if (st->isdir) { mapping(string:mixed) res = copy_collection(source, destination, behavior, overwrite, result, id); if (res && (!sizeof (res) || res->error >= 300)) { // RFC 2518 8.8.3 and 8.8.8 (error minimization). TRACE_LEAVE("Copy of collection failed."); return res; }
-
+
if (!one_level) {
foreach(find_dir(source, id), string filename) { string subsrc = combine_path_unix(source, filename); string subdst = combine_path_unix(destination, filename); SIMPLE_TRACE_ENTER(this, "Copy from %O to %O\n", subsrc, subdst); mapping(string:mixed) sub_res = recurse(subsrc, subdst); if (sub_res && !(<0, 201, 204>)[sub_res->error]) { result->add_status(subdst, sub_res->error, sub_res->rettext); } }
-
+
}
TRACE_LEAVE(""); return res; } else { TRACE_LEAVE(""); return copy_file(source, destination, behavior, overwrite, id); } }; int start_ms_size = id->multi_status_size();
-
mapping(string:mixed) res = recurse (source, destination);
+
mapping(string:mixed) res = recurse (source, destination
, one_level
);
if (res && res->error != 204 && res->error != 201) return res; else if (id->multi_status_size() != start_ms_size) return ([]); else return res; } //! Used by the default @[recurse_move_files] to move a file (and not //! a directory) from @[source] to @[destination].
Roxen.git/server/base_server/module.pike:1978:
Overwrite overwrite, RequestID id) { // Fall back to find_file(). RequestID tmp_id = id->clone_me(); tmp_id->not_query = query_location() + source; tmp_id->misc["new-uri"] = query_location() + destination; tmp_id->request_headers->destination = id->url_base() + query_location()[1..] + destination; tmp_id->method = "MOVE"; mapping(string:mixed) res = find_file(source, tmp_id);
-
if (!res || res->error != 501) return res;
+
if (!res || res->error != 501)
{
+
if (res && !sizeof(res)) {
+
foreach(tmp_id->get_multi_status()->get_responses_by_prefix("");
+
string href; MultiStatusNode status) {
+
id->set_status_for_url(href, status);
+
}
+
}
+
return res;
+
}
// Not implemented. Fall back to COPY + DELETE. string prefix = map(query_location()[1..]/"/", Roxen.http_encode_url)*"/"; MultiStatus.Prefixed result = id->get_multi_status()->prefix (id->url_base() + prefix); res = copy_collection(source, destination, behavior, overwrite, result, id); if (res && (res->error >= 300 || !sizeof(res))) { // Copy failed. return res; } int fail;