| 1 |
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-WEBDAV; Base: 10 -*- |
|---|
| 2 |
;;; $Header: /usr/local/cvsrep/cl-webdav/resources.lisp,v 1.12 2007/04/18 19:21:00 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 |
(defclass resource () |
|---|
| 33 |
((script-name :initarg :script-name |
|---|
| 34 |
:accessor resource-script-name |
|---|
| 35 |
:documentation "This slot holds the script name |
|---|
| 36 |
\(see HUNCHENTOOT:SCRIPT-NAME) that was used to create the |
|---|
| 37 |
resource. For objects you create yourself, you must provide a |
|---|
| 38 |
meaningful value that can be used to access the resource.")) |
|---|
| 39 |
(:documentation "This is the base class you'll have to subclass |
|---|
| 40 |
if you want to create your own custom DAV server. Each object of |
|---|
| 41 |
this class represents one resource on the server and most of the |
|---|
| 42 |
time these objects are created by the server using only |
|---|
| 43 |
the :SCRIPT-NAME initarg. If you need more initialization to |
|---|
| 44 |
happen, write an :AFTER method for INITIALIZE-INSTANCE. |
|---|
| 45 |
|
|---|
| 46 |
See the file `file-resources.lisp' for an example of a subclass |
|---|
| 47 |
of RESOURCE.")) |
|---|
| 48 |
|
|---|
| 49 |
;;; The generic functions which /must/ be specialized |
|---|
| 50 |
|
|---|
| 51 |
(defgeneric resource-exists (resource) |
|---|
| 52 |
(:documentation "This function must return a true value if the |
|---|
| 53 |
resource RESOURCE exists on the server and NIL otherwise. You |
|---|
| 54 |
must specialize this generic function for your own classes.")) |
|---|
| 55 |
|
|---|
| 56 |
(defgeneric resource-children (resource) |
|---|
| 57 |
(:documentation "This function must return a list of all |
|---|
| 58 |
children of RESOURCE \(which themselves are RESOURCE objects). |
|---|
| 59 |
You must specialize this generic function for your own |
|---|
| 60 |
classes.")) |
|---|
| 61 |
|
|---|
| 62 |
(defgeneric resource-parent (resource) |
|---|
| 63 |
(:documentation "This function must return a RESOURCE object |
|---|
| 64 |
which is the parent resource of RESOURCE or NIL if there is no |
|---|
| 65 |
parent. You must specialize this generic function for your own |
|---|
| 66 |
classes.")) |
|---|
| 67 |
|
|---|
| 68 |
(defgeneric resource-collection-p (resource) |
|---|
| 69 |
(:documentation "This function must return a true value if the |
|---|
| 70 |
resource RESOURCE is a collection. You must specialize this |
|---|
| 71 |
generic function for your own classes.")) |
|---|
| 72 |
|
|---|
| 73 |
(defgeneric resource-write-date (resource) |
|---|
| 74 |
(:documentation "This function must return a universal time |
|---|
| 75 |
denoting the time the resource RESOURCE was last modified. You |
|---|
| 76 |
must specialize this generic function for your own classes.")) |
|---|
| 77 |
|
|---|
| 78 |
(defgeneric resource-length (resource) |
|---|
| 79 |
(:documentation "This function must return an integer denoting |
|---|
| 80 |
the length of the resource RESOURCE in octets. You must |
|---|
| 81 |
specialize this generic function for your own classes.")) |
|---|
| 82 |
|
|---|
| 83 |
(defgeneric resource-display-name (resource) |
|---|
| 84 |
(:documentation "This function must return a string which, |
|---|
| 85 |
according to the WebDAV RFC, \"provides a name for the resource |
|---|
| 86 |
that is suitable for presentation to a user.\" You must |
|---|
| 87 |
specialize this generic function for your own classes.")) |
|---|
| 88 |
|
|---|
| 89 |
(defgeneric send-content (resource stream) |
|---|
| 90 |
(:documentation "This function is called for GET requests and |
|---|
| 91 |
must send the complete contents of the \(non-collection) resource |
|---|
| 92 |
RESOURCE to the \(flexi) stream STREAM.")) |
|---|
| 93 |
|
|---|
| 94 |
(defgeneric get-content (resource stream length) |
|---|
| 95 |
(:documentation "This function is called for PUT requests and |
|---|
| 96 |
must read LENGTH octets of data from the \(flexi) stream STREAM |
|---|
| 97 |
and store them in a place appropriate for the resource |
|---|
| 98 |
RESOURCE.")) |
|---|
| 99 |
|
|---|
| 100 |
(defgeneric remove-resource (resource) |
|---|
| 101 |
(:documentation "This function must completely remove the |
|---|
| 102 |
resource RESOURCE. It doesn't have to deal with dead properties, |
|---|
| 103 |
and it can assume that RESOURCE doesn't have children in case |
|---|
| 104 |
it's a collection.")) |
|---|
| 105 |
|
|---|
| 106 |
(defgeneric move-resource (source destination) |
|---|
| 107 |
(:documentation "This function must \"move\" the \(contents of |
|---|
| 108 |
the) resource SOURCE in such a way that it can in the future be |
|---|
| 109 |
accessed as DESTINATION. It doesn't have to deal with dead |
|---|
| 110 |
properties, and it can assume that SOURCE doesn't have children |
|---|
| 111 |
in case it's a collection.")) |
|---|
| 112 |
|
|---|
| 113 |
(defgeneric copy-resource (source destination) |
|---|
| 114 |
(:documentation "This function must \"copy\" the \(contents of |
|---|
| 115 |
the) resource SOURCE in such a way that the copy can in the |
|---|
| 116 |
future be accessed as DESTINATION. It doesn't have to deal with |
|---|
| 117 |
dead properties, and it can assume that SOURCE doesn't have |
|---|
| 118 |
children in case it's a collection.")) |
|---|
| 119 |
|
|---|
| 120 |
(defgeneric create-collection (resource) |
|---|
| 121 |
(:documentation "This function must create a collection |
|---|
| 122 |
resource that in the future can be accessed as RESOURCE.")) |
|---|
| 123 |
|
|---|
| 124 |
(defgeneric accept-request-p (resource-class request) |
|---|
| 125 |
(:documentation "This must be a function which accepts a |
|---|
| 126 |
Hunchentoot request object REQUEST and returns a generalized |
|---|
| 127 |
boolean denoting whether REQUEST denotes a resource the DAV |
|---|
| 128 |
server wants to handle. Usually, you'll want to look at the |
|---|
| 129 |
script name of the request or something like that - see the class |
|---|
| 130 |
FILE-RESOURCE for an example. |
|---|
| 131 |
|
|---|
| 132 |
Note that you specialize this function on the resource /class/ |
|---|
| 133 |
and not on the resource.") |
|---|
| 134 |
(:method ((resource-class standard-class) script-name) |
|---|
| 135 |
(accept-request-p (class-name resource-class) script-name))) |
|---|
| 136 |
|
|---|
| 137 |
;;; The generic functions which have default methods and thus don't |
|---|
| 138 |
;;; necessarily need to be specialized |
|---|
| 139 |
|
|---|
| 140 |
(defgeneric resource-creation-date (resource) |
|---|
| 141 |
(:documentation "This function must return a universal time |
|---|
| 142 |
denoting the time the resource RESOURCE was created. There's a |
|---|
| 143 |
default method which returns RESOURCE-WRITE-DATE, but most likely |
|---|
| 144 |
you'll want to specialize this for you own classes.") |
|---|
| 145 |
(:method (resource) |
|---|
| 146 |
(resource-write-date resource))) |
|---|
| 147 |
|
|---|
| 148 |
(defgeneric resource-content-type (resource) |
|---|
| 149 |
(:documentation "This function must return a string denoting |
|---|
| 150 |
the MIME type of the resource RESOURCE. It will only be called |
|---|
| 151 |
if RESOURCE is /not/ a collection. There's a default method |
|---|
| 152 |
which always returns \"application/octet-stream\", but most |
|---|
| 153 |
likely you'll want to specialize this for your own classes.") |
|---|
| 154 |
(:method (resource) "application/octet-stream")) |
|---|
| 155 |
|
|---|
| 156 |
(defgeneric resource-content-language (resource) |
|---|
| 157 |
(:documentation "This function should return either NIL or a |
|---|
| 158 |
language tag as defined in section 14.13 of RFC 2068. If the |
|---|
| 159 |
value returned by this function is not NIL, it will also be used |
|---|
| 160 |
as the `Content-Language' header returned for GET requests. |
|---|
| 161 |
There's a default method which always returns NIL.") |
|---|
| 162 |
(:method (resource))) |
|---|
| 163 |
|
|---|
| 164 |
(defgeneric resource-source (resource) |
|---|
| 165 |
(:documentation "This function should return either NIL or a |
|---|
| 166 |
DAV \"source\" XML node \(structured as an XMLS node) that, |
|---|
| 167 |
according to the WebDAV RFC, \"identifies the resource that |
|---|
| 168 |
contains the unprocessed source of the link's source.\" There's a |
|---|
| 169 |
default method which always returns NIL.") |
|---|
| 170 |
(:method (resource))) |
|---|
| 171 |
|
|---|
| 172 |
(defgeneric resource-etag (resource) |
|---|
| 173 |
(:documentation "This function should return an ETag for the |
|---|
| 174 |
resource RESOURCE or NIL. If the value returned by this function |
|---|
| 175 |
is not NIL, it will also be used as the `ETag' header returned |
|---|
| 176 |
for GET requests. There's a default method which synthesizes a |
|---|
| 177 |
value based on the script name and the write date of the |
|---|
| 178 |
resource, and in most cases you probably don't need to specialize |
|---|
| 179 |
this function.") |
|---|
| 180 |
(:method (resource) |
|---|
| 181 |
(md5-hex (format nil "~A-~A" |
|---|
| 182 |
(get-last-modified resource) |
|---|
| 183 |
(resource-script-name resource))))) |
|---|
| 184 |
|
|---|
| 185 |
(defgeneric resource-type (resource) |
|---|
| 186 |
(:documentation "This function should return either NIL or a |
|---|
| 187 |
DAV \"resourcetype\" XML node \(structured as an XMLS node) that, |
|---|
| 188 |
according to the WebDAV RFC, \"specifies the nature of the |
|---|
| 189 |
resource.\" There's a default method which returns something |
|---|
| 190 |
fitting for collections and NIL otherwise, and in most cases you |
|---|
| 191 |
probably don't need to specialize this function.") |
|---|
| 192 |
(:method (resource) |
|---|
| 193 |
(when (resource-collection-p resource) |
|---|
| 194 |
(dav-node "resourcetype" (dav-node "collection"))))) |
|---|
| 195 |
|
|---|
| 196 |
(defgeneric resource-uri-prefix (resource) |
|---|
| 197 |
(:documentation "This function must return a string which is |
|---|
| 198 |
the part of a resource's HTTP or HTTPS URI that comprises the |
|---|
| 199 |
scheme, the host, and the port and ends with a slash - something |
|---|
| 200 |
like \"http://localhost:4242/\" or \"https://www.lisp.org/\". |
|---|
| 201 |
|
|---|
| 202 |
The default method synthesizes this from the information |
|---|
| 203 |
Hunchentoot provides and usually you only have to write your own |
|---|
| 204 |
method if you're sitting behind a proxy.") |
|---|
| 205 |
(:method (resource) |
|---|
| 206 |
(format nil "http~:[~;s~]://~A~@[:~A~]/" |
|---|
| 207 |
(ssl-p) |
|---|
| 208 |
(ppcre:regex-replace ":\\d+$" (host) "") |
|---|
| 209 |
(server-port)))) |
|---|
| 210 |
|
|---|
| 211 |
(defgeneric get-dead-properties (resource) |
|---|
| 212 |
(:documentation "This function must return all dead properties |
|---|
| 213 |
of the resource RESOURCE as a list of XML elements structured as |
|---|
| 214 |
XMLS nodes. There's a default method but you should definitely |
|---|
| 215 |
specialize this for production servers.") |
|---|
| 216 |
(:method (resource) |
|---|
| 217 |
(retrieve-properties (resource-script-name resource)))) |
|---|
| 218 |
|
|---|
| 219 |
(defgeneric remove-dead-property (resource property) |
|---|
| 220 |
(:documentation "This function must remove the currently stored |
|---|
| 221 |
dead property designated by PROPERTY \(an XMLS node) of the |
|---|
| 222 |
resource RESOURCE. There's a default method but you should |
|---|
| 223 |
definitely specialize this for production servers.") |
|---|
| 224 |
(:method (resource property) |
|---|
| 225 |
(store-properties (resource-script-name resource) |
|---|
| 226 |
(remove property (get-dead-properties resource) |
|---|
| 227 |
:test #'property-equal)))) |
|---|
| 228 |
|
|---|
| 229 |
(defgeneric set-dead-property (resource property) |
|---|
| 230 |
(:documentation "This function must replace the currently |
|---|
| 231 |
stored dead property designated by PROPERTY \(an XMLS node) of |
|---|
| 232 |
the resource RESOURCE with PROPERTY, i.e. PROPERTY doubles as the |
|---|
| 233 |
property itself and as the property designator. There's a |
|---|
| 234 |
default method but you should definitely specialize this for |
|---|
| 235 |
production servers.") |
|---|
| 236 |
(:method (resource property) |
|---|
| 237 |
(store-properties (resource-script-name resource) |
|---|
| 238 |
(cons property |
|---|
| 239 |
(remove property (get-dead-properties resource) |
|---|
| 240 |
:test #'property-equal))))) |
|---|
| 241 |
|
|---|
| 242 |
(defgeneric remove-dead-properties (resource) |
|---|
| 243 |
(:documentation "This function must remove all dead properties |
|---|
| 244 |
of the resource RESOURCE. There's a default method but you |
|---|
| 245 |
should definitely specialize this for production servers.") |
|---|
| 246 |
(:method (resource) |
|---|
| 247 |
(remove-properties (resource-script-name resource)))) |
|---|
| 248 |
|
|---|
| 249 |
(defgeneric move-dead-properties (source destination) |
|---|
| 250 |
(:documentation "This function must move all dead properties of |
|---|
| 251 |
the resource SOURCE to the resource DESTINATION. There's a |
|---|
| 252 |
default method but you should definitely specialize this for |
|---|
| 253 |
production servers.") |
|---|
| 254 |
(:method (source destination) |
|---|
| 255 |
(move-properties (resource-script-name source) |
|---|
| 256 |
(resource-script-name destination)))) |
|---|
| 257 |
|
|---|
| 258 |
(defgeneric copy-dead-properties (source destination) |
|---|
| 259 |
(:documentation "This function must copy all dead properties of |
|---|
| 260 |
the resource SOURCE to the resource DESTINATION. There's a |
|---|
| 261 |
default method but you should definitely specialize this for |
|---|
| 262 |
production servers.") |
|---|
| 263 |
(:method (source destination) |
|---|
| 264 |
(copy-properties (resource-script-name source) |
|---|
| 265 |
(resource-script-name destination)))) |
|---|
| 266 |
|
|---|
| 267 |
;;; Internal functionality |
|---|
| 268 |
|
|---|
| 269 |
(defun resource-href (resource) |
|---|
| 270 |
"Returns a URL-encoded version of the resource's script name for use |
|---|
| 271 |
in HREF elements in property XML." |
|---|
| 272 |
(format nil "~:[/~;~:*~{~A~^/~}~]" |
|---|
| 273 |
(mapcar (lambda (string) |
|---|
| 274 |
(url-encode string +utf-8+)) |
|---|
| 275 |
(ppcre:split "/" (resource-script-name resource))))) |
|---|
| 276 |
|
|---|
| 277 |
(defun resource-name (resource) |
|---|
| 278 |
"Retrieves and returns the \"name part\" of the script name of |
|---|
| 279 |
RESOURCE, i.e. the last non-empty string behind a slash. Note |
|---|
| 280 |
that the result can be NIL. This is a bit similar to |
|---|
| 281 |
CL:FILE-NAMESTRING." |
|---|
| 282 |
(first (last (ppcre:split "/" (resource-script-name resource))))) |
|---|
| 283 |
|
|---|
| 284 |
(defun get-last-modified (resource) |
|---|
| 285 |
"This is the function that is called for the |
|---|
| 286 |
\"getlastmodified\" property. It returns the result of |
|---|
| 287 |
RESOURCE-WRITE-DATE as an RFC 1123 string within a DAV XML node." |
|---|
| 288 |
(let ((node (dav-node "getlastmodified" (rfc-1123-date (resource-write-date resource))))) |
|---|
| 289 |
(push '(("dt" . "urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/") "dateTime.rfc1123") |
|---|
| 290 |
(node-attributes node)) |
|---|
| 291 |
node)) |
|---|
| 292 |
|
|---|
| 293 |
(defun creation-date (resource) |
|---|
| 294 |
"This is the function that is called for the \"creationdate\" |
|---|
| 295 |
property. It returns the result of RESOURCE-CREATION-DATE as an |
|---|
| 296 |
ISO 8601 string within a DAV XML node." |
|---|
| 297 |
(let ((node (dav-node "creationdate" (iso-8601-date (resource-creation-date resource))))) |
|---|
| 298 |
(push '(("dt" . "urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/") "dateTime.tz") |
|---|
| 299 |
(node-attributes node)) |
|---|
| 300 |
node)) |
|---|
| 301 |
|
|---|
| 302 |
(defun get-content-length (resource) |
|---|
| 303 |
"This is the function that is called for the |
|---|
| 304 |
\"getcontentlength\" property. It simply returns the result of |
|---|
| 305 |
RESOURCE-LENGTH as a string." |
|---|
| 306 |
(unless (resource-collection-p resource) |
|---|
| 307 |
(format nil "~D" (resource-length resource)))) |
|---|
| 308 |
|
|---|
| 309 |
(defun get-content-type (resource) |
|---|
| 310 |
"This is the function that is called for the \"getcontenttype\" |
|---|
| 311 |
property. It simply returns the result of RESOURCE-CONTENT-TYPE |
|---|
| 312 |
for non-collections and \"httpd/unix-directory\" for |
|---|
| 313 |
collections." |
|---|
| 314 |
(cond ((resource-collection-p resource) "httpd/unix-directory") |
|---|
| 315 |
(t (resource-content-type resource)))) |
|---|
| 316 |
|
|---|
| 317 |
(defun remove-resource* (resource) |
|---|
| 318 |
"Removes the resource RESOURCE and \(if necessary) its children |
|---|
| 319 |
using REMOVE-RESOURCE. Returns a list of conses where the car is |
|---|
| 320 |
an HTTP return code and the cdr is the corresponding resource for |
|---|
| 321 |
exceptional situations encountered during the process." |
|---|
| 322 |
(unless (resource-exists resource) |
|---|
| 323 |
(return-from remove-resource* |
|---|
| 324 |
(list (cons +http-not-found+ resource)))) |
|---|
| 325 |
(when (resource-collection-p resource) |
|---|
| 326 |
;; try to remove the children first |
|---|
| 327 |
(let ((child-results |
|---|
| 328 |
(loop for child in (resource-children resource) |
|---|
| 329 |
nconc (remove-resource* child)))) |
|---|
| 330 |
(when child-results |
|---|
| 331 |
;; stop recursion if something went wrong deeper down in the |
|---|
| 332 |
;; hierarchy |
|---|
| 333 |
(return-from remove-resource* child-results)))) |
|---|
| 334 |
;; remove the dead properties first |
|---|
| 335 |
(remove-dead-properties resource) |
|---|
| 336 |
(handler-case |
|---|
| 337 |
(remove-resource resource) |
|---|
| 338 |
(error (condition) |
|---|
| 339 |
(warn "While trying to delete ~S: ~A" |
|---|
| 340 |
(resource-script-name resource) condition) |
|---|
| 341 |
(list (cons +http-internal-server-error+ resource))) |
|---|
| 342 |
(:no-error (&rest args) |
|---|
| 343 |
(declare (ignore args)) |
|---|
| 344 |
nil))) |
|---|
| 345 |
|
|---|
| 346 |
(defun copy-or-move-resource* (source destination movep depth) |
|---|
| 347 |
"Copies or moves \(depending on the generalized boolean MOVEP) |
|---|
| 348 |
the resource denoted by SOURCE to \(the resource denoted by) |
|---|
| 349 |
DESTINATION. If DEPTH is NIL, recurses down to the children \(if |
|---|
| 350 |
any) as well. Returns a list of conses where the car is an HTTP |
|---|
| 351 |
return code and the cdr is the corresponding \(source) resource |
|---|
| 352 |
for exceptional situations encountered during the process." |
|---|
| 353 |
(unless (resource-exists source) |
|---|
| 354 |
(return-from copy-or-move-resource* |
|---|
| 355 |
(list (cons +http-not-found+ source)))) |
|---|
| 356 |
;; take care of dead properties |
|---|
| 357 |
(funcall (if movep #'move-dead-properties #'copy-dead-properties) |
|---|
| 358 |
source destination) |
|---|
| 359 |
(let (results) |
|---|
| 360 |
(handler-case |
|---|
| 361 |
(funcall (if movep #'move-resource #'copy-resource) |
|---|
| 362 |
source destination) |
|---|
| 363 |
(error (condition) |
|---|
| 364 |
(warn "While trying to ~:[copy~;move~] from ~S to ~S: ~A" |
|---|
| 365 |
movep |
|---|
| 366 |
(resource-script-name source) |
|---|
| 367 |
(resource-script-name destination) |
|---|
| 368 |
condition) |
|---|
| 369 |
(push (cons +http-internal-server-error+ source) results))) |
|---|
| 370 |
(cond ((and (resource-collection-p source) |
|---|
| 371 |
(null depth) |
|---|
| 372 |
;; only recurse if there weren't any errors |
|---|
| 373 |
(null results)) |
|---|
| 374 |
(loop for source-child in (resource-children source) |
|---|
| 375 |
for destination-child = (get-resource |
|---|
| 376 |
;; synthesize script name for new child |
|---|
| 377 |
(format nil "~A~A" |
|---|
| 378 |
(resource-script-name destination) |
|---|
| 379 |
(resource-name source-child))) |
|---|
| 380 |
nconc (copy-or-move-resource* source-child destination-child depth movep))) |
|---|
| 381 |
(t results)))) |
|---|
| 382 |
|
|---|
| 383 |
(defgeneric create-resource (resource-class script-name) |
|---|
| 384 |
(:documentation "Creates and returns an object of type |
|---|
| 385 |
RESOURCE-CLASS \(a subclass of RESOURCE) corresponding to the script |
|---|
| 386 |
name SCRIPT-NAME \(which is already URL-decoded).") |
|---|
| 387 |
(:method ((resource-class standard-class) script-name) |
|---|
| 388 |
(create-resource (class-name resource-class) script-name)) |
|---|
| 389 |
(:method ((resource-class-name symbol) script-name) |
|---|
| 390 |
(make-instance resource-class-name |
|---|
| 391 |
:script-name script-name))) |
|---|
| 392 |
|
|---|
| 393 |
(defun get-resource (&optional (script-name (url-decode* (script-name*)))) |
|---|
| 394 |
"Creates and returns an object of the type stored in |
|---|
| 395 |
*RESOURCE-CLASS* corresponding to the script name SCRIPT-NAME." |
|---|
| 396 |
(create-resource *resource-class* script-name)) |
|---|
| 397 |
|
|---|
| 398 |
(defun resource-created (resource) |
|---|
| 399 |
"Utility function which sets up Hunchentoot's *REPLY* object |
|---|
| 400 |
for a +HTTP-CREATED+ response corresponding to the newly-created |
|---|
| 401 |
resource RESOURCE." |
|---|
| 402 |
(setf (content-type) (get-content-type resource) |
|---|
| 403 |
(header-out :location) (resource-script-name resource) |
|---|
| 404 |
(return-code) +http-created+) |
|---|
| 405 |
(let ((etag (resource-etag resource)) |
|---|
| 406 |
(content-language (resource-content-language resource))) |
|---|
| 407 |
(when etag |
|---|
| 408 |
(setf (header-out :etag) etag)) |
|---|
| 409 |
(when content-language |
|---|
| 410 |
(setf (header-out :content-language) content-language))) |
|---|
| 411 |
nil) |
|---|