Roxen.git
/
server
/
modules
/
misc
/
webdav.pike
version
»
Context lines:
10
20
40
80
file
none
3
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)) {