webdav.pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2006 Thomas Bopp, Thorsten Hampel, Ludger Merkens
2  *
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  *
17  * $Id: webdav.pike,v 1.3 2010/08/18 20:32:45 astra Exp $
18  */
19 
20 constant cvs_version="$Id: webdav.pike,v 1.3 2010/08/18 20:32:45 astra Exp $";
21 
22 inherit "http";
23 
24 import webdavlib;
25 
26 #include <macros.h>
27 #include <classes.h>
28 #include <attributes.h>
29 #include <config.h>
30 #include <database.h>
31 
32 //#define WEBDAV_DEBUG
33 
34 #ifdef WEBDAV_DEBUG
35 #define DAV_WERR(s, args...) werror(s+"\n", args)
36 #else
37 #define DAV_WERR(s, args...)
38 #endif
39 
40 import httplib;
41 
42 
43 class steamDAV {
44  inherit WebdavHandler;
45 
46  object _dav;
47  object __fp;
48 
49  mapping null_ressources = ([ ]);
50 
51  void create(object dav, object fp) {
52  _dav = dav;
53  __fp = fp;
54  }
55  string url_name(string fname) {
56  return replace_uml(fname);
57  }
58 
59  string lock(mixed ctx, string fname, mapping lock_data) {
60  string token;
61 
62  if ( !lock_data->token ) {
63  token = Locking.generate_token(ctx);
64  lock_data->token = token;
65  }
66  else
67  token = lock_data->token;
68 
69  if ( objectp(ctx) ) {
70  mapping ldata = ctx->query_attribute(OBJ_LOCK) || ([ ]);
71  ldata[token] = lock_data;
72  ctx->set_attribute(OBJ_LOCK, ldata);
73  }
74  else {
75  mapping ldata = null_ressources[fname] || ([ ]);
76  ldata[token] = lock_data;
77  null_ressources[fname] = ldata | ([ "isnull": 1, ]) ;
78  }
79  return lock_data->token;
80  }
81 
82  void unlock(mixed ctx, string fname, void|string token)
83  {
84  if ( !stringp(token) )
85  ctx->set_attribute(OBJ_LOCK, 0);
86  else {
87  mapping ldata = ctx->query_attribute(OBJ_LOCK);
88  if ( mappingp(ldata) ) {
89  m_delete(ldata, token);
90  ctx->set_attribute(OBJ_LOCK, ldata);
91  }
92  }
93  }
94 
95  mapping get_locks(mixed ctx, string fname) {
96  if ( !objectp(ctx) )
97  return null_ressources[fname];
98  return ctx->query_attribute(OBJ_LOCK) || ([ ]);
99  }
100 
101  mapping is_locked(mixed ctx, string fname, void|string gottoken) {
102  mapping ldata;
103  if ( !objectp(ctx) )
104  ldata = null_ressources[fname] || ([ ]);
105  else
106  ldata = ctx->query_attribute(OBJ_LOCK) || ([ ]);
107  DAV_WERR("is_locked(%O, %s, %O)", ctx, fname, gottoken);
108  DAV_WERR("locks = %O\n", ldata);
109  foreach(indices(ldata), string token) {
110  mapping lockdata = ldata[token];
111  if ( mappingp(lockdata) ) {
112  int timeout = 180;
113  if ( stringp(lockdata["timeout"]) ) {
114  sscanf(lockdata["timeout"], "Second-%d", timeout);
115  }
116  if ( lockdata->locktime > 0 && (time() - lockdata->locktime) < timeout )
117  {
118  DAV_WERR("active lock found...");
119  if ( !stringp(gottoken) || lockdata->token == gottoken )
120  return lockdata;
121  }
122  }
123  }
124  if ( objectp(ctx) ) {
125  object env = ctx->get_environment();
126  ldata = is_locked(env, "", gottoken);
127  if ( mappingp(ldata) )
128  return ldata;
129  }
130  if ( !stringp(gottoken) ) {
131  if ( objectp(ctx) )
132  catch(ctx->set_attribute(OBJ_LOCK, 0));
133  else
134  null_ressources[fname] = 0;
135  }
136  return 0;
137  }
138  string get_user_href() {
139  return _Server->get_server_name() + "/~" + this_user()->get_user_name();
140  }
141 
142  string get_etag(mixed ctx) {
143  return ctx->get_etag();
144  }
145 
146  object get_object_id() { return _dav->get_object_id(); }
147  object this() { return _dav->get_user_object(); }
148 }
149 
150 static object __webdavHandler;
151 
152 int check_lock(object obj, mapping vars, void|string fname)
153 {
154  if ( !objectp(obj) && !stringp(fname) )
155  return 1;
156  if ( !stringp(fname) )
157  fname = "";
158  string token = get_opaquelocktoken(__request->request_headers->if);
159  if ( stringp(token) ) {
160  if ( mappingp(__webdavHandler->is_locked(obj, fname, token)) )
161  return 1;
162  }
163  mapping res = __webdavHandler->is_locked(obj, fname);
164  DAV_WERR("Checking lock (current token=%O) locked=%O", token, res);
165  return res == 0;
166 }
167 
168 int check_precondition(object obj)
169 {
170  int res = ::check_precondition(obj);
171  if ( !res )
172  return 0;
173 
174  array ifheader = webdavlib.parse_if_header(__request->request_headers->if);
175  if ( sizeof(ifheader) == 0 )
176  return 1;
177 
178  foreach ( ifheader, mapping list) {
179  object condobj = obj;
180  string path = list->resource || __request->not_query;
181  if ( stringp(list->resource) ) {
182  condobj = _fp->path_to_object(list->resource);
183  }
184  if ( stringp(list->state) ) {
185  // check state
186  if ( search(list->state, "opaquelocktoken") >= 0 ) {
187  mapping islocked =__webdavHandler->is_locked(condobj,path,list->state);
188  if ( objectp(__webdavHandler) && !mappingp(islocked) ) {
189  continue; // match needs resource to be locked
190  }
191  }
192  else if ( list->state == "DAV:no-lock" ) {
193  mapping locks = __webdavHandler->get_locks(condobj, path);
194  if ( !list->not ) {
195  continue;
196  }
197  }
198  if ( stringp(list->entity) )
199  if ( objectp(condobj) && list->entity != condobj->get_etag() )
200  continue;
201  return 1;
202  }
203  else if ( stringp(list->entity) ) {
204  if ( objectp(condobj) && list->entity != condobj->get_etag() )
205  continue;
206  return 1;
207  }
208  }
209  return 0;
210 }
211 
212 mapping handle_OPTIONS(object obj, mapping variables)
213 {
214  mapping result = ::handle_OPTIONS(obj, variables);
215  result->extra_heads += ([
216  "MS-Author-Via": "DAV",
217 #ifdef WEBDAV_CLASS2
218  "DAV": "1,2",
219 #else
220  "DAV": "1",
221 #endif
222  ]);
223  return result;
224 }
225 
226 #ifdef WEBDAV_CLASS2
227 mapping handle_LOCK(object obj, mapping variables)
228 {
229  if ( this_user() == USER("guest") )
230  return response_noaccess(obj, variables);
231 
232  obj = _fp->path_to_object(__request->not_query, 1);
233  if ( !check_precondition(obj) )
234  return low_answer(412, "Precondition Failed");
235 
236  return lock(__request->not_query, __request->request_headers,
237  __request->body_raw,
238  __webdavHandler, obj);
239 }
240 
241 mapping handle_UNLOCK(object obj, mapping variables)
242 {
243  if ( this_user() == USER("guest") )
244  return response_noaccess(obj, variables);
245  obj = _fp->path_to_object(__request->not_query, 1);
246  if ( !check_precondition(obj) )
247  return low_answer(412, "Precondition Failed");
248 
249  return unlock(__request->not_query, __request->request_headers,
250  __request->body_raw,
251  __webdavHandler, obj);
252 }
253 #endif
254 
255 static bool move_and_rename(object obj, string name)
256 {
257  string fname, dname, sname;
258 
259  sname = _Server->get_server_name();
260  sscanf(name, "%*s://" + _Server->get_server_name() + "%s", name);
261 
262  if ( name[-1] == '/' )
263  name = dirname(name);
264 
265  fname = basename(name);
266  dname = dirname(name);
267  object target;
268  if ( dname == "" )
269  target = obj->get_environment();
270  else
271  target = _fp->path_to_object(dname);
272 
273 
274  if ( !objectp(target) ) {
275  DAV_WERR("No Target directory found at %s", dname);
276  return false;
277  }
278  if ( strlen(fname) > 0 )
279  obj->set_attribute(OBJ_NAME, fname);
280  obj->move(target);
281  return true;
282 }
283 
284 mapping|void handle_MOVE(object obj, mapping variables)
285 {
286  string destination = __request->request_headers->destination;
287  string overwrite = __request->request_headers->overwrite;
288 
289  if ( !check_precondition(obj) )
290  return low_answer(412, "Precondition Failed");
291 
292  if ( !check_lock(obj, variables) )
293  return low_answer(423, "Locked");
294 
295  if ( !objectp(obj) )
296  return response_notfound(__request->not_query, variables);
297 
298  if ( !stringp(overwrite) )
299  overwrite = "T";
300  __request->misc->overwrite = overwrite;
301  __request->misc->destination = resolve_destination(
302  destination, __request->request_headers->host);
303 
304  int res = 201;
305  // create copy variables before calling filesystem module
306  if ( mappingp(__request->misc->destination) )
307  return __request->misc->destination;
308  else if ( stringp(__request->misc->destination) )
309  __request->misc["new-uri"] = __request->misc->destination;
310  DAV_WERR("Handling move:misc=\n"+sprintf("%O\n", __request->misc));
311 
312  destination = __request->misc["new-uri"];
313  if ( catch(destination = url_to_string(destination)) )
314  FATAL("Failed to convert destination %s", destination);
315 
316  object dest = _fp->path_to_object(destination);
317  if ( objectp(dest) ) {
318  if ( __request->misc->overwrite == "F" ) {
319  DAV_WERR("overwritting failed !");
320  return low_answer(412, "Pre-Condition Failed");
321  }
322  else {
323  if ( !check_lock(dest, variables) )
324  return low_answer(423, "Locked");
325  res = 204;
326  dest->delete();
327  }
328  }
329  if ( !move_and_rename(obj, destination) )
330  return low_answer(409, "conflict");
331  return low_answer(res, "moved");
332 }
333 
334 mapping|void handle_MKCOL(object obj, mapping variables)
335 {
336  if ( strlen(__request->body_raw) > 0 )
337  return low_answer(415, "unsupported type");
338  // todo: read the body ?!
339  if ( !check_precondition(obj) )
340  return low_answer(412, "Precondition Failed");
341  mapping result = ::handle_MKDIR(obj, variables);
342  if ( mappingp(result) && (result->error == 200 || !result->error) )
343  return low_answer(201, "Created");
344  return result;
345 }
346 
347 mapping|void handle_COPY(object obj, mapping variables)
348 {
349  string destination = __request->request_headers->destination;
350  string overwrite = __request->request_headers->overwrite;
351 
352  if ( !check_precondition(obj) )
353  return low_answer(412, "Precondition Failed");
354 
355  if ( !stringp(overwrite) )
356  overwrite = "T";
357  __request->misc->overwrite = overwrite;
358  __request->misc->destination = resolve_destination(
359  destination, __request->request_headers->host);
360  if ( mappingp(__request->misc->destination) )
361  return __request->misc->destination;
362 
363  mixed result = ([ ]); // should now how to copy handle_http();
364  object duplicate;
365 
366  duplicate = _fp->path_to_object(__request->misc->destination);
367 
368  DAV_WERR("Handling COPY:misc=\n"+sprintf("%O\n", __request->misc));
369  DAV_WERR("Found dest resource = %s !",
370  (objectp(duplicate) ? "yes" : "no"));
371  int res = 201;
372  if ( objectp(duplicate) ) {
373  if ( __request->misc->overwrite == "F" ) {
374  DAV_WERR("overwritting failed !");
375  return low_answer(412, "conflict");
376  }
377  else {
378  if ( !check_lock(duplicate, variables) )
379  return low_answer(423, "Locked");
380  res = 204;
381  duplicate->delete();
382  }
383  }
384  if ( objectp(obj) )
385  {
386  if ( obj->get_object_class() & CLASS_CONTAINER )
387  duplicate = obj->duplicate(true);
388  else
389  duplicate= obj->duplicate();
390 
391  // dirname and fname
392  if ( !move_and_rename(duplicate, __request->misc->destination) )
393  return low_answer(409, "conflict");
394  duplicate->set_attribute(OBJ_LOCK, 0);
395  return low_answer(res, "copied");
396  }
397  else {
398  FATAL("Resource could not be found !");
399  return low_answer(404, "not found");
400  }
401 }
402 
403 mapping|void handle_PROPPATCH(object obj, mapping variables)
404 {
405  obj = _fp->path_to_object(__request->not_query, 1);
406 
407  if ( !check_lock(obj, variables) )
408  return low_answer(423, "Locked");
409 
410  if ( !check_precondition(obj) )
411  return low_answer(412, "Precondition Failed");
412 
413  return proppatch(__request->not_query, __request->request_headers,
414  __request->body_raw, __webdavHandler, obj);
415 }
416 
417 mapping handle_DELETE(object obj, mapping vars)
418 {
419 
420  if ( !check_lock(obj, vars) )
421  return low_answer(423, "Locked");
422  return ::handle_DELETE(obj, vars);
423 }
424 
425 mapping handle_PUT(object obj, mapping vars)
426 {
427  string fname = __request->not_query;
428 
429  obj = _fp->path_to_object(__request->not_query, 1);
430 
431 
432  if ( !check_lock(obj, vars, fname) )
433  return low_answer(423, "Locked");
434 
435  if ( !check_precondition(obj) )
436  return low_answer(412, "Precondition Failed");
437 
438  mapping result = ::handle_PUT(obj, vars);
439  mixed err = catch {
440  mapping locks = __webdavHandler->get_locks(0, fname);
441  if ( mappingp(locks) && sizeof(locks) > 0 ) {
442  // locked null resources
443  object fp = vars->fp;
444  if ( !objectp(fp) )
445  fp = get_module("filepath:tree");
446  obj = fp->path_to_object(fname);
447  if ( objectp(obj) ) {
448  if ( !mappingp(obj->query_attribute(OBJ_LOCK)) )
449  obj->set_attribute(OBJ_LOCK, locks);
450  }
451  }
452  };
453  if ( err ) {
454  FATAL("While setting lock for previous null resource: %O", err);
455  }
456  return result;
457 }
458 
459 mapping|void handle_PROPFIND(object obj, mapping variables)
460 {
461  isWebDAV = 1; // heuristics ;-)
462 
463  obj = _fp->path_to_object(__request->not_query, 1);
464 
465  if ( !objectp(obj) )
466  return low_answer(404, "not found");
467 
468  return propfind(__request->not_query, __request->request_headers,
469  __request->body_raw, __webdavHandler, obj);
470 }
471 
472 mixed get_property(object obj, Property property)
473 {
474  if ( !objectp(obj) )
475  return 0;
476  if ( !objectp(property) )
477  error("No property found, null-pointer !");
478  DAV_WERR("Get property %s, val=%O, ns=%O", property->get_name(),obj->query_attribute(property->get_name()), property->describe_namespace());
479  string pname = property->get_ns_name();
480 
481 #if 0
482  switch( property->get_name() ) {
483  case "displayname":
484  return obj->query_attribute(OBJ_NAME);
485  case "name":
486  return obj->get_identifier();
487  }
488 #endif
489  mixed res = obj->query_attribute(pname);
490  if ( stringp(res) )
491  return replace(res, ({ "<", ">" }), ({ "&lt;", "&gt;" }));
492  return res;
493 }
494 
495 int set_property(object obj, Property property, mapping namespaces)
496 {
497  string val = property->get_value();
498  string xmlns = property->describe_namespace();
499  DAV_WERR("Set property %s", property->_sprintf());
500 
501  obj->set_attribute(property->get_ns_name(), val);
502  return 1;
503 }
504 
505 string resolve_redirect(object link)
506 {
507  object res = link->get_link_object();
508  return _fp->object_to_filename(res);
509 }
510 
511 object get_context(object ctx, string f)
512 {
513  if ( objectp(ctx) )
514  return ctx->get_object_byname(f);
515  return 0;
516 }
517 
518 int is_link(object ctx)
519 {
520  if ( objectp(ctx) && ctx->get_object_class() & CLASS_LINK )
521  return 1;
522  return 0;
523 }
524 
525 static mapping
526 call_command(string cmd, object obj, mapping variables)
527 {
528  mapping result = ([ ]);
529 
530  // overwritten - must not forward requests without trailing /
531  DAV_WERR("RAW: %s", __request->raw);
532  float f = gauge {
533  function call = this_object()["handle_"+cmd];
534  if ( functionp(call) ) {
535  result = call(obj, variables);
536  }
537  else {
538  result->error = 501;
539  result->data = "Not implemented";
540  }
541  if ( mappingp(result) ) {
542  if ( stringp(result->data) && strlen(result->data) > 0 &&
543  !result->encoding && !result->type )
544  {
545  result->tags = 0;
546  result->encoding = "utf-8";
547  result->type = "text/xml";
548  }
549  }
550  };
551 
552  DAV_WERR("DAV: %s %s %f seconds", cmd, __request->not_query, f);
553  return result;
554 }
555 
556 void create(object fp, bool admin_port)
557 {
558  ::create(fp, admin_port);
559  __webdavHandler = steamDAV(this_object(), fp);
560  __webdavHandler->get_directory = fp->get_directory;
561  __webdavHandler->stat_file = fp->stat_file;
562  __webdavHandler->set_property = set_property;
563  __webdavHandler->get_property = get_property;
564  __webdavHandler->resolve_redirect = resolve_redirect;
565  __webdavHandler->get_context = get_context;
566  __webdavHandler->is_link = is_link;
567 }
568 
569 void respond(object req, mapping result)
570 {
571  DAV_WERR("Respond: %O", result);
572  if ( stringp(result->data) )
573  result->length = strlen(result->data);
574 
575  ::respond(req, result);
576 }
577 
578 string get_identifier()
579 {
580  if ( is_dav() )
581  return "webdav";
582  else
583  return "http";
584 }
585 
586 void test()
587 {
588  webdavlib.test();
589 }