(url-cookie-setup-save-timer): Avoid warnings.
[bpt/emacs.git] / lisp / url / url-dav.el
CommitLineData
8c8b8430
SM
1;;; url-dav.el --- WebDAV support
2
3;; Copyright (C) 2001 Free Software Foundation, Inc.
4
5;; Author: Bill Perry <wmperry@gnu.org>
6;; Maintainer: Bill Perry <wmperry@gnu.org>
8c8b8430
SM
7;; Keywords: url, vc
8
9;; GNU Emacs is free software; you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation; either version 2, or (at your option)
12;; any later version.
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
20;; along with GNU Emacs; see the file COPYING. If not, write to the
21;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22;; Boston, MA 02111-1307, USA.
23
24(eval-when-compile
25 (require 'cl))
26
27(require 'xml)
28(require 'url-util)
29(require 'url-handlers)
30
31(defvar url-dav-supported-protocols '(1 2)
32 "List of supported DAV versions.")
33
34;;;###autoload
35(defun url-dav-supported-p (url)
36 (and (featurep 'xml)
37 (fboundp 'xml-expand-namespace)
38 (intersection url-dav-supported-protocols
39 (plist-get (url-http-options url) 'dav))))
40
41(defun url-dav-node-text (node)
42 "Return the text data from the XML node NODE."
43 (mapconcat (lambda (txt)
44 (if (stringp txt)
45 txt
46 "")) (xml-node-children node) " "))
47
48\f
49;;; Parsing routines for the actual node contents.
50;;;
51;;; I am not incredibly happy with how this code looks/works right
52;;; now, but it DOES work, and if we get the API right, our callers
53;;; won't have to worry about the internal representation.
54
55(defconst url-dav-datatype-attribute
56 'urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/dt)
57
58(defun url-dav-process-integer-property (node)
59 (truncate (string-to-number (url-dav-node-text node))))
60
61(defun url-dav-process-number-property (node)
62 (string-to-number (url-dav-node-text node)))
63
64(defconst url-dav-iso8601-regexp
65 (let* ((dash "-?")
66 (colon ":?")
67 (4digit "\\([0-9][0-9][0-9][0-9]\\)")
68 (2digit "\\([0-9][0-9]\\)")
69 (date-fullyear 4digit)
70 (date-month 2digit)
71 (date-mday 2digit)
72 (time-hour 2digit)
73 (time-minute 2digit)
74 (time-second 2digit)
75 (time-secfrac "\\(\\.[0-9]+\\)?")
76 (time-numoffset (concat "[-+]\\(" time-hour "\\):" time-minute))
77 (time-offset (concat "Z" time-numoffset))
78 (partial-time (concat time-hour colon time-minute colon time-second
79 time-secfrac))
80 (full-date (concat date-fullyear dash date-month dash date-mday))
81 (full-time (concat partial-time time-offset))
82 (date-time (concat full-date "T" full-time)))
83 (list (concat "^" full-date)
84 (concat "T" partial-time)
85 (concat "Z" time-numoffset)))
86 "List of regular expressions matching iso8601 dates.
871st regular expression matches the date.
882nd regular expression matches the time.
893rd regular expression matches the (optional) timezone specification.
90")
91
92(defun url-dav-process-date-property (node)
93 (require 'parse-time)
94 (let* ((date-re (nth 0 url-dav-iso8601-regexp))
95 (time-re (nth 1 url-dav-iso8601-regexp))
96 (tz-re (nth 2 url-dav-iso8601-regexp))
97 (date-string (url-dav-node-text node))
98 re-start
99 time seconds minute hour fractional-seconds
100 day month year day-of-week dst tz)
101 ;; We need to populate 'time' with
102 ;; (SEC MIN HOUR DAY MON YEAR DOW DST TZ)
103
104 ;; Nobody else handles iso8601 correctly, lets do it ourselves.
105 (when (string-match date-re date-string re-start)
106 (setq year (string-to-int (match-string 1 date-string))
107 month (string-to-int (match-string 2 date-string))
108 day (string-to-int (match-string 3 date-string))
109 re-start (match-end 0))
110 (when (string-match time-re date-string re-start)
111 (setq hour (string-to-int (match-string 1 date-string))
112 minute (string-to-int (match-string 2 date-string))
113 seconds (string-to-int (match-string 3 date-string))
114 fractional-seconds (string-to-int (or
115 (match-string 4 date-string)
116 "0"))
117 re-start (match-end 0))
118 (when (string-match tz-re date-string re-start)
119 (setq tz (match-string 1 date-string)))
120 (url-debug 'dav "Parsed iso8601%s date" (if tz "tz" ""))
121 (setq time (list seconds minute hour day month year day-of-week dst tz))))
122
123 ;; Fall back to having Gnus do fancy things for us.
124 (when (not time)
125 (setq time (parse-time-string date-string)))
126
127 (if time
128 (setq time (apply 'encode-time time))
129 (url-debug 'dav "Unable to decode date (%S) (%s)"
130 (xml-node-name node) date-string))
131 time))
132
133(defun url-dav-process-boolean-property (node)
134 (/= 0 (string-to-int (url-dav-node-text node))))
135
136(defun url-dav-process-uri-property (node)
137 ;; Returns a parsed representation of the URL...
138 (url-generic-parse-url (url-dav-node-text node)))
139
140(defun url-dav-find-parser (node)
141 "Find a function to parse the XML node NODE."
142 (or (get (xml-node-name node) 'dav-parser)
143 (let ((fn (intern (format "url-dav-process-%s" (xml-node-name node)))))
144 (if (not (fboundp fn))
145 (setq fn 'url-dav-node-text)
146 (put (xml-node-name node) 'dav-parser fn))
147 fn)))
148
149(defmacro url-dav-dispatch-node (node)
150 `(funcall (url-dav-find-parser ,node) ,node))
151
152(defun url-dav-process-DAV:prop (node)
153 ;; A prop node has content model of ANY
154 ;;
155 ;; Some predefined nodes have special meanings though.
156 ;;
157 ;; DAV:supportedlock - list of DAV:lockentry
158 ;; DAV:source
159 ;; DAV:iscollection - boolean
160 ;; DAV:getcontentlength - integer
161 ;; DAV:ishidden - boolean
162 ;; DAV:getcontenttype - string
163 ;; DAV:resourcetype - node who's name is the resource type
164 ;; DAV:getlastmodified - date
165 ;; DAV:creationdate - date
166 ;; DAV:displayname - string
167 ;; DAV:getetag - unknown
168 (let ((children (xml-node-children node))
169 (node-type nil)
170 (props nil)
171 (value nil)
172 (handler-func nil))
173 (when (not children)
174 (error "No child nodes in DAV:prop"))
175
176 (while children
177 (setq node (car children)
178 node-type (intern
179 (or
180 (cdr-safe (assq url-dav-datatype-attribute
181 (xml-node-attributes node)))
182 "unknown"))
183 value nil)
184
185 (case node-type
186 ((dateTime.iso8601tz
187 dateTime.iso8601
188 dateTime.tz
189 dateTime.rfc1123
190 dateTime
191 date) ; date is our 'special' one...
192 ;; Some type of date/time string.
193 (setq value (url-dav-process-date-property node)))
194 (int
195 ;; Integer type...
196 (setq value (url-dav-process-integer-property node)))
197 ((number float)
198 (setq value (url-dav-process-number-property node)))
199 (boolean
200 (setq value (url-dav-process-boolean-property node)))
201 (uri
202 (setq value (url-dav-process-uri-property node)))
203 (otherwise
204 (if (not (eq node-type 'unknown))
205 (url-debug 'dav "Unknown data type in url-dav-process-prop: %s"
206 node-type))
207 (setq value (url-dav-dispatch-node node))))
208
209 (setq props (plist-put props (xml-node-name node) value)
210 children (cdr children)))
211 props))
212
213(defun url-dav-process-DAV:supportedlock (node)
214 ;; DAV:supportedlock is a list of DAV:lockentry items.
215 ;; DAV:lockentry in turn contains a DAV:lockscope and DAV:locktype.
216 ;; The DAV:lockscope must have a single node beneath it, ditto for
217 ;; DAV:locktype.
218 (let ((children (xml-node-children node))
219 (results nil)
220 scope type)
221 (while children
222 (when (and (not (stringp (car children)))
223 (eq (xml-node-name (car children)) 'DAV:lockentry))
224 (setq scope (assq 'DAV:lockscope (xml-node-children (car children)))
225 type (assq 'DAV:locktype (xml-node-children (car children))))
226 (when (and scope type)
227 (setq scope (xml-node-name (car (xml-node-children scope)))
228 type (xml-node-name (car (xml-node-children type))))
229 (push (cons type scope) results)))
230 (setq children (cdr children)))
231 results))
232
233(defun url-dav-process-subnode-property (node)
234 ;; Returns a list of child node names.
235 (delq nil (mapcar 'car-safe (xml-node-children node))))
236
237(defalias 'url-dav-process-DAV:depth 'url-dav-process-integer-property)
238(defalias 'url-dav-process-DAV:resourcetype 'url-dav-process-subnode-property)
239(defalias 'url-dav-process-DAV:locktype 'url-dav-process-subnode-property)
240(defalias 'url-dav-process-DAV:lockscope 'url-dav-process-subnode-property)
241(defalias 'url-dav-process-DAV:getcontentlength 'url-dav-process-integer-property)
242(defalias 'url-dav-process-DAV:getlastmodified 'url-dav-process-date-property)
243(defalias 'url-dav-process-DAV:creationdate 'url-dav-process-date-property)
244(defalias 'url-dav-process-DAV:iscollection 'url-dav-process-boolean-property)
245(defalias 'url-dav-process-DAV:ishidden 'url-dav-process-boolean-property)
246
247(defun url-dav-process-DAV:locktoken (node)
248 ;; DAV:locktoken can have one or more DAV:href children.
249 (delq nil (mapcar (lambda (n)
250 (if (stringp n)
251 n
252 (url-dav-dispatch-node n)))
253 (xml-node-children node))))
254
255(defun url-dav-process-DAV:owner (node)
256 ;; DAV:owner can contain anything.
257 (delq nil (mapcar (lambda (n)
258 (if (stringp n)
259 n
260 (url-dav-dispatch-node n)))
261 (xml-node-children node))))
262
263(defun url-dav-process-DAV:activelock (node)
264 ;; DAV:activelock can contain:
265 ;; DAV:lockscope
266 ;; DAV:locktype
267 ;; DAV:depth
268 ;; DAV:owner (optional)
269 ;; DAV:timeout (optional)
270 ;; DAV:locktoken (optional)
271 (let ((children (xml-node-children node))
272 (results nil))
273 (while children
274 (if (listp (car children))
275 (push (cons (xml-node-name (car children))
276 (url-dav-dispatch-node (car children)))
277 results))
278 (setq children (cdr children)))
279 results))
280
281(defun url-dav-process-DAV:lockdiscovery (node)
282 ;; Can only contain a list of DAV:activelock objects.
283 (let ((children (xml-node-children node))
284 (results nil))
285 (while children
286 (cond
287 ((stringp (car children))
288 ;; text node? why?
289 nil)
290 ((eq (xml-node-name (car children)) 'DAV:activelock)
291 (push (url-dav-dispatch-node (car children)) results))
292 (t
293 ;; Ignore unknown nodes...
294 nil))
295 (setq children (cdr children)))
296 results))
297
298(defun url-dav-process-DAV:status (node)
299 ;; The node contains a standard HTTP/1.1 response line... we really
300 ;; only care about the numeric status code.
301 (let ((status (url-dav-node-text node)))
302 (if (string-match "\\`[ \r\t\n]*HTTP/[0-9.]+ \\([0-9]+\\)" status)
303 (string-to-int (match-string 1 status))
304 500)))
305
306(defun url-dav-process-DAV:propstat (node)
307 ;; A propstate node can have the following children...
308 ;;
309 ;; DAV:prop - a list of properties and values
310 ;; DAV:status - An HTTP/1.1 status line
311 (let ((children (xml-node-children node))
312 (props nil)
313 (status nil))
314 (when (not children)
315 (error "No child nodes in DAV:propstat"))
316
317 (setq props (url-dav-dispatch-node (assq 'DAV:prop children))
318 status (url-dav-dispatch-node (assq 'DAV:status children)))
319
320 ;; Need to parse out the HTTP status
321 (setq props (plist-put props 'DAV:status status))
322 props))
323
324(defun url-dav-process-DAV:response (node)
325 (let ((children (xml-node-children node))
326 (propstat nil)
327 (href))
328 (when (not children)
329 (error "No child nodes in DAV:response"))
330
331 ;; A response node can have the following children...
332 ;;
333 ;; DAV:href - URL the response is for.
334 ;; DAV:propstat - see url-dav-process-propstat
335 ;; DAV:responsedescription - text description of the response
336 (setq propstat (assq 'DAV:propstat children)
337 href (assq 'DAV:href children))
338
339 (when (not href)
340 (error "No href in DAV:response"))
341
342 (when (not propstat)
343 (error "No propstat in DAV:response"))
344
345 (setq propstat (url-dav-dispatch-node propstat)
346 href (url-dav-dispatch-node href))
347 (cons href propstat)))
348
349(defun url-dav-process-DAV:multistatus (node)
350 (let ((children (xml-node-children node))
351 (results nil))
352 (while children
353 (push (url-dav-dispatch-node (car children)) results)
354 (setq children (cdr children)))
355 results))
356
357\f
358;;; DAV request/response generation/processing
359(defun url-dav-process-response (buffer url)
360 "Parses a WebDAV response from BUFFER, interpreting it relative to URL.
361
362The buffer must have been retrieved by HTTP or HTTPS and contain an
363XML document.
364"
365 (declare (special url-http-content-type
366 url-http-response-status
367 url-http-end-of-headers))
368 (let ((tree nil)
369 (overall-status nil))
370 (when buffer
371 (unwind-protect
372 (save-excursion
373 (set-buffer buffer)
374 (goto-char url-http-end-of-headers)
375 (setq overall-status url-http-response-status)
376
377 ;; XML documents can be transferred as either text/xml or
378 ;; application/xml, and we are required to accept both of
379 ;; them.
380 (if (and
381 url-http-content-type
382 (or (string-match "^text/xml" url-http-content-type)
383 (string-match "^application/xml" url-http-content-type)))
384 (setq tree (xml-parse-region (point) (point-max)))))
385 ;; Clean up after ourselves.
386 '(kill-buffer buffer)))
387
388 ;; We should now be
389 (if (eq (xml-node-name (car tree)) 'DAV:multistatus)
390 (url-dav-dispatch-node (car tree))
391 (url-debug 'dav "Got back singleton response for URL(%S)" url)
392 (let ((properties (url-dav-dispatch-node (car tree))))
393 ;; We need to make sure we have a DAV:status node in there for
394 ;; higher-level code;
395 (setq properties (plist-put properties 'DAV:status overall-status))
396 ;; Make this look like a DAV:multistatus parse tree so that
397 ;; nobody but us needs to know the difference.
398 (list (cons url properties))))))
399
400(defun url-dav-request (url method tag body
401 &optional depth headers namespaces)
402 "Performs WebDAV operation METHOD on URL. Returns the parsed responses.
403Automatically creates an XML request body if TAG is non-nil.
404BODY is the XML document fragment to be enclosed by <TAG></TAG>.
405
406DEPTH is how deep the request should propogate. Default is 0, meaning
407it should apply only to URL. A negative number means to use
408`Infinity' for the depth. Not all WebDAV servers support this depth
409though.
410
411HEADERS is an assoc list of extra headers to send in the request.
412
413NAMESPACES is an assoc list of (NAMESPACE . EXPANSION), and these are
414added to the <TAG> element. The DAV=DAV: namespace is automatically
415added to this list, so most requests can just pass in nil.
416"
417 ;; Take care of the default value for depth...
418 (setq depth (or depth 0))
419
420 ;; Now lets translate it into something webdav can understand.
421 (if (< depth 0)
422 (setq depth "Infinity")
423 (setq depth (int-to-string depth)))
424 (if (not (assoc "DAV" namespaces))
425 (setq namespaces (cons '("DAV" . "DAV:") namespaces)))
426
427 (let* ((url-request-extra-headers `(("Depth" . ,depth)
428 ("Content-type" . "text/xml")
429 ,@headers))
430 (url-request-method method)
431 (url-request-data
432 (if tag
433 (concat
434 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
435 "<" (symbol-name tag) " "
436 ;; add in the appropriate namespaces...
437 (mapconcat (lambda (ns)
438 (concat "xmlns:" (car ns) "='" (cdr ns) "'"))
439 namespaces "\n ")
440 ">\n"
441 body
442 "</" (symbol-name tag) ">\n"))))
443 (url-dav-process-response (url-retrieve-synchronously url) url)))
444
445;;;###autoload
446(defun url-dav-get-properties (url &optional attributes depth namespaces)
447 "Return properties for URL, up to DEPTH levels deep.
448
449Returns an assoc list, where the key is the filename (possibly a full
450URI), and the value is a standard property list of DAV property
451names (ie: DAV:resourcetype).
452"
453 (url-dav-request url "PROPFIND" 'DAV:propfind
454 (if attributes
455 (mapconcat (lambda (attr)
456 (concat "<DAV:prop><"
457 (symbol-name attr)
458 "/></DAV:prop>"))
459 attributes "\n ")
460 " <DAV:allprop/>")
461 depth nil namespaces))
462
463(defmacro url-dav-http-success-p (status)
464 "Return whether PROPERTIES was the result of a successful DAV request."
465 `(= (/ (or ,status 500) 100) 2))
466
467\f
468;;; Locking support
469(defvar url-dav-lock-identifier (concat "mailto:" user-mail-address)
470 "*URL used as contact information when creating locks in DAV.
471This will be used as the contents of the DAV:owner/DAV:href tag to
472identify the owner of a LOCK when requesting it. This will be shown
473to other users when the DAV:lockdiscovery property is requested, so
474make sure you are comfortable with it leaking to the outside world.
475")
476
477;;;###autoload
478(defun url-dav-lock-resource (url exclusive &optional depth)
479 "Request a lock on URL. If EXCLUSIVE is non-nil, get an exclusive lock.
480Optional 3rd argument DEPTH says how deep the lock should go, default is 0
481\(lock only the resource and none of its children\).
482
483Returns a cons-cell of (SUCCESSFUL-RESULTS . FAILURE-RESULTS).
484SUCCESSFUL-RESULTS is a list of (URL STATUS locktoken).
485FAILURE-RESULTS is a list of (URL STATUS).
486"
487 (setq exclusive (if exclusive "<DAV:exclusive/>" "<DAV:shared/>"))
488 (let* ((body
489 (concat
490 " <DAV:lockscope>" exclusive "</DAV:lockscope>\n"
491 " <DAV:locktype> <DAV:write/> </DAV:locktype>\n"
492 " <DAV:owner>\n"
493 " <DAV:href>" url-dav-lock-identifier "</DAV:href>\n"
494 " </DAV:owner>\n"))
495 (response nil) ; Responses to the LOCK request
496 (result nil) ; For walking thru the response list
497 (child-url nil)
498 (child-status nil)
499 (failures nil) ; List of failure cases (URL . STATUS)
500 (successes nil)) ; List of success cases (URL . STATUS)
501 (setq response (url-dav-request url "LOCK" 'DAV:lockinfo body
502 depth '(("Timeout" . "Infinite"))))
503
504 ;; Get the parent URL ready for expand-file-name
505 (if (not (vectorp url))
506 (setq url (url-generic-parse-url url)))
507
508 ;; Walk thru the response list, fully expand the URL, and grab the
509 ;; status code.
510 (while response
511 (setq result (pop response)
512 child-url (url-expand-file-name (pop result) url)
513 child-status (or (plist-get result 'DAV:status) 500))
514 (if (url-dav-http-success-p child-status)
515 (push (list url child-status "huh") successes)
516 (push (list url child-status) failures)))
517 (cons successes failures)))
518
519;;;###autoload
520(defun url-dav-active-locks (url &optional depth)
521 "Return an assoc list of all active locks on URL."
522 (let ((response (url-dav-get-properties url '(DAV:lockdiscovery) depth))
523 (properties nil)
524 (child nil)
525 (child-url nil)
526 (child-results nil)
527 (results nil))
528 (if (not (vectorp url))
529 (setq url (url-generic-parse-url url)))
530
531 (while response
532 (setq child (pop response)
533 child-url (pop child)
534 child-results nil)
535 (when (and (url-dav-http-success-p (plist-get child 'DAV:status))
536 (setq child (plist-get child 'DAV:lockdiscovery)))
537 ;; After our parser has had its way with it, The
538 ;; DAV:lockdiscovery property is a list of DAV:activelock
539 ;; objects, which are comprised of DAV:activelocks, which
540 ;; assoc lists of properties and values.
541 (while child
542 (if (assq 'DAV:locktoken (car child))
543 (let ((tokens (cdr (assq 'DAV:locktoken (car child))))
544 (owners (cdr (assq 'DAV:owner (car child)))))
545 (dolist (token tokens)
546 (dolist (owner owners)
547 (push (cons token owner) child-results)))))
548 (pop child)))
549 (if child-results
550 (push (cons (url-expand-file-name child-url url) child-results)
551 results)))
552 results))
553
554;;;###autoload
555(defun url-dav-unlock-resource (url lock-token)
556 "Release the lock on URL represented by LOCK-TOKEN.
557Returns `t' iff the lock was successfully released.
558"
559 (declare (special url-http-response-status))
560 (let* ((url-request-extra-headers (list (cons "Lock-Token"
561 (concat "<" lock-token ">"))))
562 (url-request-method "UNLOCK")
563 (url-request-data nil)
564 (buffer (url-retrieve-synchronously url))
565 (result nil))
566 (when buffer
567 (unwind-protect
568 (save-excursion
569 (set-buffer buffer)
570 (setq result (url-dav-http-success-p url-http-response-status)))
571 (kill-buffer buffer)))
572 result))
573
574\f
575;;; file-name-handler stuff
576(defun url-dav-file-attributes-mode-string (properties)
577 (let ((modes (make-string 10 ?-))
578 (supported-locks (plist-get properties 'DAV:supportedlock))
579 (executable-p (equal (plist-get properties 'http://apache.org/dav/props/executable)
580 "T"))
581 (directory-p (memq 'DAV:collection (plist-get properties 'DAV:resourcetype)))
582 (readable t)
583 (lock nil))
584 ;; Assume we can read this, otherwise the PROPFIND would have
585 ;; failed.
586 (when readable
587 (aset modes 1 ?r)
588 (aset modes 4 ?r)
589 (aset modes 7 ?r))
590
591 (when directory-p
592 (aset modes 0 ?d))
593
594 (when executable-p
595 (aset modes 3 ?x)
596 (aset modes 6 ?x)
597 (aset modes 9 ?x))
598
599 (while supported-locks
600 (setq lock (car supported-locks)
601 supported-locks (cdr supported-locks))
602 (case (car lock)
603 (DAV:write
604 (case (cdr lock)
605 (DAV:shared ; group permissions (possibly world)
606 (aset modes 5 ?w))
607 (DAV:exclusive
608 (aset modes 2 ?w)) ; owner permissions?
609 (otherwise
610 (url-debug 'dav "Unrecognized DAV:lockscope (%S)" (cdr lock)))))
611 (otherwise
612 (url-debug 'dav "Unrecognized DAV:locktype (%S)" (car lock)))))
613 modes))
614
615;;;###autoload
616(defun url-dav-file-attributes (url)
617 (let ((properties (cdar (url-dav-get-properties url)))
618 (attributes nil))
619 (if (and properties
620 (url-dav-http-success-p (plist-get properties 'DAV:status)))
621 ;; We got a good DAV response back..
622 (setq attributes
623 (list
624 ;; t for directory, string for symbolic link, or nil
625 ;; Need to support DAV Bindings to figure out the
626 ;; symbolic link issues.
627 (if (memq 'DAV:collection (plist-get properties 'DAV:resourcetype)) t nil)
628
629 ;; Number of links to file... Needs DAV Bindings.
630 1
631
632 ;; File uid - no way to figure out?
633 0
634
635 ;; File gid - no way to figure out?
636 0
637
638 ;; Last access time - ???
639 nil
640
641 ;; Last modification time
642 (plist-get properties 'DAV:getlastmodified)
643
644 ;; Last status change time... just reuse last-modified
645 ;; for now.
646 (plist-get properties 'DAV:getlastmodified)
647
648 ;; size in bytes
649 (or (plist-get properties 'DAV:getcontentlength) 0)
650
651 ;; file modes as a string like `ls -l'
652 ;;
653 ;; Should be able to build this up from the
654 ;; DAV:supportedlock attribute pretty easily. Getting
655 ;; the group info could be impossible though.
656 (url-dav-file-attributes-mode-string properties)
657
658 ;; t iff file's gid would change if it were deleted &
659 ;; recreated. No way for us to know that thru DAV.
660 nil
661
662 ;; inode number - meaningless
663 nil
664
665 ;; device number - meaningless
666 nil))
667 ;; Fall back to just the normal http way of doing things.
668 (setq attributes (url-http-head-file-attributes url)))
669 attributes))
670
671;;;###autoload
672(defun url-dav-save-resource (url obj &optional content-type lock-token)
673 "Save OBJ as URL using WebDAV.
674URL must be a fully qualified URL.
675OBJ may be a buffer or a string."
676 (let ((buffer nil)
677 (result nil)
678 (url-request-extra-headers nil)
679 (url-request-method "PUT")
680 (url-request-data
681 (cond
682 ((bufferp obj)
683 (save-excursion
684 (set-buffer obj)
685 (buffer-string)))
686 ((stringp obj)
687 obj)
688 (t
689 (error "Invalid object to url-dav-save-resource")))))
690
691 (if lock-token
692 (push
693 (cons "If" (concat "(<" lock-token ">)"))
694 url-request-extra-headers))
695
696 ;; Everything must always have a content-type when we submit it.
697 (push
698 (cons "Content-type" (or content-type "application/octet-stream"))
699 url-request-extra-headers)
700
701 ;; Do the save...
702 (setq buffer (url-retrieve-synchronously url))
703
704 ;; Sanity checking
705 (when buffer
706 (unwind-protect
707 (save-excursion
708 (set-buffer buffer)
709 (setq result (url-dav-http-success-p url-http-response-status)))
710 (kill-buffer buffer)))
711 result))
712
713(eval-when-compile
714 (defmacro url-dav-delete-something (url lock-token &rest error-checking)
715 "Delete URL completely, with no sanity checking whatsoever. DO NOT USE.
716This is defined as a macro that will not be visible from compiled files.
717Use with care, and even then think three times.
718"
719 `(progn
720 ,@error-checking
721 (url-dav-request ,url "DELETE" nil nil -1
722 (if ,lock-token
723 (list
724 (cons "If"
725 (concat "(<" ,lock-token ">)"))))))))
726
727
728;;;###autoload
729(defun url-dav-delete-directory (url &optional recursive lock-token)
730 "Delete the WebDAV collection URL.
731If optional second argument RECURSIVE is non-nil, then delete all
732files in the collection as well.
733"
734 (let ((status nil)
735 (props nil)
736 (props nil))
737 (setq props (url-dav-delete-something
738 url lock-token
739 (setq props (url-dav-get-properties url '(DAV:getcontenttype) 1))
740 (if (and (not recursive)
741 (/= (length props) 1))
742 (signal 'file-error (list "Removing directory"
743 "directory not empty" url)))))
744
745 (mapc (lambda (result)
746 (setq status (plist-get (cdr result) 'DAV:status))
747 (if (not (url-dav-http-success-p status))
748 (signal 'file-error (list "Removing directory"
749 "Errror removing"
750 (car result) status))))
751 props))
752 nil)
753
754;;;###autoload
755(defun url-dav-delete-file (url &optional lock-token)
756 "Delete file named URL."
757 (let ((props nil)
758 (status nil))
759 (setq props (url-dav-delete-something
760 url lock-token
761 (setq props (url-dav-get-properties url))
762 (if (eq (plist-get (cdar props) 'DAV:resourcetype) 'DAV:collection)
763 (signal 'file-error (list "Removing old name" "is a collection" url)))))
764
765 (mapc (lambda (result)
766 (setq status (plist-get (cdr result) 'DAV:status))
767 (if (not (url-dav-http-success-p status))
768 (signal 'file-error (list "Removing old name"
769 "Errror removing"
770 (car result) status))))
771 props))
772 nil)
773
774;;;###autoload
775(defun url-dav-directory-files (url &optional full match nosort files-only)
776 "Return a list of names of files in DIRECTORY.
777There are three optional arguments:
778If FULL is non-nil, return absolute file names. Otherwise return names
779 that are relative to the specified directory.
780If MATCH is non-nil, mention only file names that match the regexp MATCH.
781If NOSORT is non-nil, the list is not sorted--its order is unpredictable.
782 NOSORT is useful if you plan to sort the result yourself.
783"
784 (let ((properties (url-dav-get-properties url '(DAV:resourcetype) 1))
785 (child-url nil)
786 (child-props nil)
787 (files nil)
788 (parsed-url (url-generic-parse-url url)))
789
790 (if (= (length properties) 1)
791 (signal 'file-error (list "Opening directory" "not a directory" url)))
792
793 (while properties
794 (setq child-props (pop properties)
795 child-url (pop child-props))
796 (if (and (eq (plist-get child-props 'DAV:resourcetype) 'DAV:collection)
797 files-only)
798 ;; It is a directory, and we were told to return just files.
799 nil
800
801 ;; Fully expand the URL and then rip off the beginning if we
802 ;; are not supposed to return fully-qualified names.
803 (setq child-url (url-expand-file-name child-url parsed-url))
804 (if (not full)
805 (setq child-url (substring child-url (length url))))
806
807 ;; We don't want '/' as the last character in filenames...
808 (if (string-match "/$" child-url)
809 (setq child-url (substring child-url 0 -1)))
810
811 ;; If we have a match criteria, then apply it.
812 (if (or (and match (not (string-match match child-url)))
813 (string= child-url "")
814 (string= child-url url))
815 nil
816 (push child-url files))))
817
818 (if nosort
819 files
820 (sort files 'string-lessp))))
821
822;;;###autoload
823(defun url-dav-file-directory-p (url)
824 "Return t if URL names an existing DAV collection."
825 (let ((properties (cdar (url-dav-get-properties url '(DAV:resourcetype)))))
826 (eq (plist-get properties 'DAV:resourcetype) 'DAV:collection)))
827
828;;;###autoload
829(defun url-dav-make-directory (url &optional parents)
830 "Create the directory DIR and any nonexistent parent dirs."
831 (declare (special url-http-response-status))
832 (let* ((url-request-extra-headers nil)
833 (url-request-method "MKCOL")
834 (url-request-data nil)
835 (buffer (url-retrieve-synchronously url))
836 (result nil))
837 (when buffer
838 (unwind-protect
839 (save-excursion
840 (set-buffer buffer)
841 (case url-http-response-status
842 (201 ; Collection created in its entirety
843 (setq result t))
844 (403 ; Forbidden
845 nil)
846 (405 ; Method not allowed
847 nil)
848 (409 ; Conflict
849 nil)
850 (415 ; Unsupported media type (WTF?)
851 nil)
852 (507 ; Insufficient storage
853 nil)
854 (otherwise
855 nil)))
856 (kill-buffer buffer)))
857 result))
858
859;;;###autoload
860(defun url-dav-rename-file (oldname newname &optional overwrite)
861 (if (not (and (string-match url-handler-regexp oldname)
862 (string-match url-handler-regexp newname)))
863 (signal 'file-error "Cannot rename between different URL backends" oldname newname))
864
865 (let* ((headers nil)
866 (props nil)
867 (status nil)
868 (directory-p (url-dav-file-directory-p oldname))
869 (exists-p (url-http-file-exists-p newname)))
870
871 (if (and exists-p
872 (or
873 (null overwrite)
874 (and (numberp overwrite)
875 (not (yes-or-no-p
876 (format "File %s already exists; rename to it anyway? "
877 newname))))))
878 (signal 'file-already-exists (list "File already exists" newname)))
879
880 ;; Honor the overwrite flag...
881 (if overwrite (push '("Overwrite" . "T") headers))
882
883 ;; Have to tell them where to copy it to!
884 (push (cons "Destination" newname) headers)
885
886 ;; Always send a depth of -1 in case we are moving a collection.
887 (setq props (url-dav-request oldname "MOVE" nil nil (if directory-p -1 0)
888 headers))
889
890 (mapc (lambda (result)
891 (setq status (plist-get (cdr result) 'DAV:status))
892
893 (if (not (url-dav-http-success-p status))
894 (signal 'file-error (list "Renaming" oldname newname status))))
895 props)
896 t))
897
898;;;###autoload
899(defun url-dav-file-name-all-completions (file url)
900 "Return a list of all completions of file name FILE in directory DIRECTORY.
901These are all file names in directory DIRECTORY which begin with FILE.
902"
903 (url-dav-directory-files url nil (concat "^" file ".*")))
904
905;;;###autoload
906(defun url-dav-file-name-completion (file url)
907 "Complete file name FILE in directory DIRECTORY.
908Returns the longest string
909common to all file names in DIRECTORY that start with FILE.
910If there is only one and FILE matches it exactly, returns t.
911Returns nil if DIR contains no name starting with FILE.
912"
913 (let ((matches (url-dav-file-name-all-completions file url))
914 (result nil))
915 (cond
916 ((null matches)
917 ;; No matches
918 nil)
919 ((and (= (length matches) 1)
920 (string= file (car matches)))
921 ;; Only one file and FILE matches it exactly...
922 t)
923 (t
924 ;; Need to figure out the longest string that they have in commmon
925 (setq matches (sort matches (lambda (a b) (> (length a) (length b)))))
926 (let ((n (length file))
927 (searching t)
928 (regexp nil)
929 (failed nil))
930 (while (and searching
931 (< n (length (car matches))))
932 (setq regexp (concat "^" (substring (car matches) 0 (1+ n)))
933 failed nil)
934 (dolist (potential matches)
935 (if (not (string-match regexp potential))
936 (setq failed t)))
937 (if failed
938 (setq searching nil)
939 (incf n)))
940 (substring (car matches) 0 n))))))
941
942(defun url-dav-register-handler (op)
943 (put op 'url-file-handlers (intern-soft (format "url-dav-%s" op))))
944
945(mapcar 'url-dav-register-handler
946 '(file-name-all-completions
947 file-name-completion
948 rename-file
949 make-directory
950 file-directory-p
951 directory-files
952 delete-file
953 delete-directory
954 file-attributes))
955
956\f
957;;; Version Control backend cruft
958
959;(put 'vc-registered 'url-file-handlers 'url-dav-vc-registered)
960
961;;;###autoload
962(defun url-dav-vc-registered (url)
963 (if (and (string-match "\\`https?" url)
964 (plist-get (url-http-options url) 'dav))
965 (progn
966 (vc-file-setprop url 'vc-backend 'dav)
967 t)))
968
969\f
970;;; Miscellaneous stuff.
971
972(provide 'url-dav)
e5566bd5
MB
973
974;;; arch-tag: 2b14b7b3-888a-49b8-a490-17276a40e78e