root/trunk/thirdparty/cl-webdav/handlers.lisp

Revision 3342, 15.3 kB (checked in by edi, 7 months ago)

Update copyright for next release

  • Property svn:executable set to
Line 
1 ;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-WEBDAV; Base: 10 -*-
2 ;;; $Header: /usr/local/cvsrep/cl-webdav/handlers.lisp,v 1.13 2007/05/19 22:34:35 edi Exp $
3
4 ;;; Copyright (c) 2007-2008, Dr. Edmund Weitz.  All rights reserved.
5
6 ;;; Redistribution and use in source and binary forms, with or without
7 ;;; modification, are permitted provided that the following conditions
8 ;;; are met:
9
10 ;;;   * Redistributions of source code must retain the above copyright
11 ;;;     notice, this list of conditions and the following disclaimer.
12
13 ;;;   * Redistributions in binary form must reproduce the above
14 ;;;     copyright notice, this list of conditions and the following
15 ;;;     disclaimer in the documentation and/or other materials
16 ;;;     provided with the distribution.
17
18 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
19 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 ;;; ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
22 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
24 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 (in-package :cl-webdav)
31
32 (defun dav-dispatcher (request &optional (resource-class *resource-class*))
33   "A generic Hunchentoot dispatcher \(corresponding to the
34 resource class RESOURCE-CLASS) for all DAV methods.  The handler
35 which is returned will have *RESOURCE-CLASS* bound to
36 RESOURCE-CLASS.  In theory, you could use this as your dispatcher
37 \(which doesn't call ACCEPT-REQUEST-P first), but it's not
38 exported and only used internally by CREATE-DAV-DISPATCHER."
39   (let ((handler (case (request-method request)
40                    (:options 'options-handler)
41                    (:propfind 'propfind-handler)
42                    (:get 'get-handler)
43                    (:head 'head-handler)
44                    (:proppatch 'proppatch-handler)
45                    (:put 'put-handler)
46                    (:copy 'copy-handler)
47                    (:move 'move-handler)
48                    (:mkcol 'mkcol-handler)
49                    (:delete 'delete-handler)
50                    (otherwise 'not-implemented))))
51     (lambda ()
52       (let ((*resource-class* resource-class))
53         (funcall handler)))))
54
55 (defun options-dispatcher (request)
56   "A dispatcher which'll dispatch to OPTIONS-HANDLER in case of
57 an OPTIONS request and decline otherwise.  This is only useful if
58 you want to cater to Microsoft DAV clients which always
59 unconditionally send OPTIONS requests to the \"/\" root
60 resource.  Sigh..."
61   (case (request-method request)
62     (:options 'options-handler)))
63
64 (defgeneric create-dav-dispatcher (resource-class &optional ms-workaround-p)
65   (:documentation "Creates and returns a dispatcher for the class
66 RESOURCE-CLASS which must be a subclass of RESOURCE.  If
67 MS-WORKAROUND-P is true \(which is the default), OPTIONS requests are
68 always handled irrespective of the results of ACCEPT-REQUEST-P - this
69 is needed to work around problems with some Microsoft DAV clients.")
70   (:method ((resource-class standard-class) &optional (ms-workaround-p t))
71    (create-dav-dispatcher (class-name resource-class) ms-workaround-p))
72   (:method ((resource-class symbol) &optional (ms-workaround-p t))
73    (lambda (request)
74      (cond ((accept-request-p resource-class request)
75             (dav-dispatcher request resource-class))
76            (ms-workaround-p (options-dispatcher request))))))
77
78 (defun options-handler ()
79   "The handler for OPTIONS requests.  Output is basically
80 determined by *ALLOWED-METHODS* and *DAV-COMPLIANCE-CLASSES*."
81   (setf (header-out :allow) (format nil "~{~A~^, ~}" *allowed-methods*)
82         (header-out :dav) (format nil "~{~D~^,~}" *dav-compliance-classes*)
83         ;; Win2k wants this - sigh...
84         (header-out :ms-author-via) "DAV")
85   ;; no content
86   nil)
87
88 (defun propfind-handler ()
89   "The handler for PROPFIND requests.  Parses the request's
90 content body \(if there is one) and returns a corresponding
91 \"multistatus\" XML element using the methods for live and dead
92 properties."
93   (let* ((depth-header (header-in* :depth))
94          (depth-value (cond ((or (null depth-header)
95                                  (string-equal depth-header "infinity")) nil)
96                             ((string= depth-header "0") 0)
97                             ((string= depth-header "1") 1)
98                             (t (warn "Depth header is ~S." depth-header)
99                                (bad-request))))
100          (initial-resource (get-resource)))
101     (unless (resource-exists initial-resource)
102       (not-found))
103     (multiple-value-bind (properties propname)
104         (parse-propfind (raw-post-data :force-binary t))
105       (setf (content-type) "text/xml; charset=utf-8"
106             (return-code) +http-multi-status+)
107       (let ((result
108              ;; loop through the resource and its descendants until
109              ;; depth limit is reached
110              (loop for depth = depth-value then (if depth (1- depth) nil)
111                    for resources = (list initial-resource)
112                    then (and (or (null depth) (not (minusp depth)))
113                              (mapcan #'resource-children resources))
114                    while resources
115                    nconc (loop for resource in resources
116                                collect (collect-properties resource
117                                                            properties
118                                                            (not propname))))))
119         (serialize-xmls-node (apply #'dav-node "multistatus" result))))))
120
121 (defun proppatch-handler ()
122   "The handler for PROPPATCH requests.  Parses the request's
123 content body, modifies the dead properties as specified and
124 returns a corresponding \"multistatus\" XML element."
125   (let ((resource (get-resource)))
126     (unless (resource-exists resource)
127       (not-found))
128     ;; RESULTS will be a list of conses where the car is the STATUS
129     ;; and the cdr is the property which was to be removed or set
130     (let (results)
131       ;; loop through all "actions" which are "set" or "remove" nodes
132       (dolist (action (node-children
133                        (parse-dav (raw-post-data :force-binary t) "propertyupdate")))
134         ;; the function to apply, i.e. what to do with the property
135         (let ((property-handler (if (equal (local-name action) "remove")
136                                   #'remove-dead-property
137                                   #'set-dead-property)))
138           ;; loop through the properties which are the children of the
139           ;; "prop" element within the "set" or "remove" element
140           (dolist (property (node-children (first (node-children action))))
141             ;; skip whitespace (which hasn't been removed as the
142             ;; "spec" is :ANY)
143             (cond ((whitespace-string-p property))
144                   ((dav-property-function property)
145                    (push (cons +http-conflict+ property) results))
146                   (t (funcall property-handler resource property)
147                      (push (cons +http-ok+ property) results))))))
148       (setf (content-type) "text/xml; charset=utf-8"
149             (return-code) +http-multi-status+)
150       (serialize-xmls-node
151        (dav-node "multistatus"
152                  (apply #'dav-node "response"
153                         (dav-node "href" (resource-href resource))
154                         (loop for (status . property) in results
155                               collect (dav-node "propstat"
156                                                 (dav-node "prop" (remove-content property))
157                                                 (dav-node "status" (status-line status))))))))))
158
159 (defun get-handler (&optional head-request-p)
160   "The handler for GET requests.  Serves the contents of the
161 resource using SEND-CONTENT and sets up the HTTP headers
162 correctly.  Also doubles as handler for HEAD requests if
163 HEAD-REQUEST-P is true."
164   (let ((resource (get-resource)))
165     (unless (resource-exists resource)
166       (not-found))
167     (when (resource-collection-p resource)
168       (forbidden))
169     (let ((etag (resource-etag resource))
170           (write-date (resource-write-date resource))
171           (content-language (resource-content-language resource)))
172       (setf (content-type) (resource-content-type resource))
173       (when etag
174         (setf (header-out :etag) etag))
175       (when content-language
176         (setf (header-out :content-language) content-language))
177       (catch 'handler-done
178         (handle-if-modified-since write-date)
179         (when (equal etag (header-in* :if-none-match))
180           (setf (return-code) +http-not-modified+)))
181       (when (eql (return-code) +http-not-modified+)
182         (throw 'handler-done nil))
183       (setf (header-out :last-modified) (rfc-1123-date write-date)
184             (content-length) (resource-length resource))
185       (unless head-request-p
186         (send-content resource (send-headers))))))
187
188 (defun head-handler ()
189   "The handler for HEAD requests - the actual work is done by
190 GET-HANDLER."
191   (get-handler t))
192
193 (defun multi-status (results &optional (default-return-code +http-no-content+))
194   "Utility function which returns a MULTISTATUS response to the
195 HTTP client which is based on RESULTS.  RESULTS must be a list of
196 conses where the cdr is the resource and the car is the
197 corresponding status code.  If RESULTS is NIL, not MUTILSTATUS
198 response will be generated and DEFAULT-RETURN-CODE will be used
199 instead."
200   (unless results
201     (setf (return-code) default-return-code)
202     (throw 'handler-done nil))
203   (setf (content-type) "text/xml; charset=utf-8"
204         (return-code) +http-multi-status+)
205   ;; use a hash table to group by status code
206   (let ((status-hash (make-hash-table)))
207     (loop for (status . resource) in results
208           do (push resource (gethash status status-hash)))
209     (let ((responses
210            (loop for status being the hash-keys of status-hash
211                  using (hash-value resources)
212                  collect (apply #'dav-node "response"
213                                 `(,@(loop for resource in resources
214                                           collect (dav-node "href" (resource-href resource)))
215                                   ,(dav-node "status" (status-line status)))))))
216       (serialize-xmls-node (apply #'dav-node "multistatus" responses)))))
217
218 (defun delete-handler ()
219   "The handler for DELETE requests.  Uses REMOVE-RESOURCE* to do
220 the actual work."
221   (let ((depth-header (header-in* :depth)))
222     (unless (or (null depth-header)
223                 (string-equal depth-header "infinity"))
224       (warn "Depth header is ~S." depth-header)
225       (bad-request)))
226   (let ((resource (get-resource)))
227     (unless (resource-exists resource)
228       (not-found))
229     (multi-status (remove-resource* resource))))
230
231 (defun put-handler ()
232   "The handler for PUT requests.  Uses GET-CONTENT to create a
233 new resource from the contents sent by the client."
234   (let* ((resource (get-resource))
235          (name (resource-name resource)))
236     (when (or (null name)
237               (whitespace-string-p name))
238       (forbidden))
239     (when (and (resource-exists resource)
240                (resource-collection-p resource))
241       (conflict))
242     (let ((parent (resource-parent resource)))
243       (when (or (null parent) (not (resource-exists parent)))
244         (conflict)))
245     (let* ((content-length-header (cdr (assoc :content-length (headers-in*))))
246            (content-length (and content-length-header
247                                 (parse-integer content-length-header :junk-allowed t))))
248       (unless content-length
249         (bad-request))
250       (get-content resource (raw-post-data :want-stream t) content-length))
251     (resource-created resource)))
252
253 (defun copy-handler (&optional movep)
254   "The handler for COPY requests which internally uses
255 COPY-OR-MOVE-RESOURCE* to do the actual work.  Also doubles as a
256 handler for MOVE requests if MOVEP is true."
257   (let* ((depth-header (header-in* :depth))
258          (depth-value (cond ((or (null depth-header)
259                                  (string-equal depth-header "infinity")) nil)
260                             ((and (string= depth-header "0")
261                                   (not movep)) 0)
262                             (t (warn "Depth header is ~S." depth-header)
263                                (bad-request))))
264          (overwrite (equal (header-in* :overwrite) "T"))
265          (source (get-resource)))
266     ;; note that we ignore a possible request body and thus the
267     ;; "propertybehaviour" XML element for now - we just try to use
268     ;; best effort to copy/move all properties
269     (unless (resource-exists source)
270       (not-found))
271     (let ((destination-header (header-in* :destination)))
272       (unless destination-header
273         (warn "No 'Destination' header.")
274         (bad-request))
275       (when (ppcre:scan "^https?://" destination-header)
276         ;; it's an absolute destination header
277         (let ((uri-prefix (resource-uri-prefix source)))
278           (unless (starts-with-p destination-header uri-prefix)
279             ;; the URI prefix must match
280             (bad-gateway))
281           ;; compute destination by stripping off the prefix
282           (setq destination-header
283                 (subseq destination-header (1- (length uri-prefix))))))
284       (let* ((destination (get-resource (url-decode* destination-header)))
285              (destination-exists (resource-exists destination)))
286         ;; make sure we aren't creating an infinite loop
287         (loop for parent = destination then (resource-parent parent)
288               while (and parent (resource-exists parent))
289               when (string= (resource-script-name parent)
290                             (resource-script-name source))
291               do (forbidden))
292         (when destination-exists
293           (unless overwrite
294             (precondition-failed))
295           ;; according to the RFC we must remove the destination first
296           (when (remove-resource* destination)
297             (failed-dependency)))
298         (let ((results (copy-or-move-resource* source destination movep depth-value)))
299           (cond (results (multi-status results))
300                 (destination-exists (setf (return-code) +http-no-content+
301                                           (content-type) nil)
302                                     nil)
303                 (t (resource-created destination))))))))
304
305 (defun move-handler ()
306   "The handler for MOVE requests.  Calls COPY-HANDLER to do the
307 actual work."
308   (copy-handler t))
309
310 (defun mkcol-handler ()
311   "The handler for MKCOL requests which uses CREATE-COLLECTION
312 internally."
313   (let ((resource (get-resource)))
314     (when (resource-exists resource)
315       (setf (header-out :allow)
316             (format nil "~{~A~^, ~}"
317                     (set-difference *allowed-methods* '(:get :head :mkcol))))
318       (method-not-allowed))
319     (let ((parent (resource-parent resource)))
320       (unless (and parent (resource-exists parent))
321         (conflict)))
322     (handler-case
323         (create-collection resource)
324       (error (condition)
325         (warn "While trying to create collection ~S: ~A"
326               (resource-script-name resource) condition)
327         (setf (return-code) +http-internal-server-error+))
328       (:no-error (&rest args)
329         (declare (ignore args))
330         (resource-created resource)))))
Note: See TracBrowser for help on using the browser.