1
# -*- test-case-name: twisted.web2.dav.test.test_copy,twisted.web2.dav.test.test_move -*-
3
# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
5
# Permission is hereby granted, free of charge, to any person obtaining a copy
6
# of this software and associated documentation files (the "Software"), to deal
7
# in the Software without restriction, including without limitation the rights
8
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
# copies of the Software, and to permit persons to whom the Software is
10
# furnished to do so, subject to the following conditions:
12
# The above copyright notice and this permission notice shall be included in all
13
# copies or substantial portions of the Software.
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
# DRI: Wilfredo Sanchez, wsanchez@apple.com
27
WebDAV COPY and MOVE methods.
30
__all__ = ["http_COPY", "http_MOVE"]
32
from twisted.python import log
33
from twisted.web2 import responsecode
34
from twisted.web2.http import HTTPError, StatusResponse
35
from twisted.web2.filter.location import addLocation
36
from twisted.web2.dav.idav import IDAVResource
37
from twisted.web2.dav.fileop import copy, move
39
# FIXME: This is circular
40
import twisted.web2.dav.static
42
def http_COPY(self, request):
44
Respond to a COPY request. (RFC 2518, section 8.8)
47
destination, destination_uri, depth = r
49
# May need to add a location header
50
addLocation(request, destination_uri)
52
return copy(self.fp, destination.fp, destination_uri, depth)
54
d = prepareForCopy(self, request)
58
def http_MOVE(self, request):
60
Respond to a MOVE request. (RFC 2518, section 8.9)
63
destination, destination_uri, depth = r
66
# RFC 2518, section 8.9 says that we must act as if the Depth header is set
67
# to infinity, and that the client must omit the Depth header or set it to
70
# This seems somewhat at odds with the notion that a bad request should be
71
# rejected outright; if the client sends a bad depth header, the client is
72
# broken, and section 8 suggests that a bad request should be rejected...
74
# Let's play it safe for now and ignore broken clients.
76
if self.fp.isdir() and depth != "infinity":
77
msg = "Client sent illegal depth header value for MOVE: %s" % (depth,)
79
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
81
# May need to add a location header
82
addLocation(request, destination_uri)
84
return move(self.fp, request.uri, destination.fp, destination_uri, depth)
86
d = prepareForCopy(self, request)
90
def prepareForCopy(self, request):
95
depth = request.headers.getHeader("depth", "infinity")
97
if depth not in ("0", "infinity"):
98
msg = ("Client sent illegal depth header value: %s" % (depth,))
100
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
103
# Verify this resource exists
106
if not self.exists():
107
log.err("File not found: %s" % (self.fp.path,))
108
raise HTTPError(StatusResponse(
109
responsecode.NOT_FOUND,
110
"Source resource %s not found." % (request.uri,)
114
# Get the destination
117
destination_uri = request.headers.getHeader("destination")
119
if not destination_uri:
120
msg = "No destination header in %s request." % (request.method,)
122
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
124
d = request.locateResource(destination_uri)
125
d.addCallback(_prepareForCopy, destination_uri, request, depth)
129
def _prepareForCopy(destination, destination_uri, request, depth):
131
# Destination must be a DAV resource
135
destination = IDAVResource(destination)
137
log.err("Attempt to %s to a non-DAV resource: (%s) %s"
138
% (request.method, destination.__class__, destination_uri))
139
raise HTTPError(StatusResponse(
140
responsecode.FORBIDDEN,
141
"Destination %s is not a WebDAV resource." % (destination_uri,)
145
# FIXME: Right now we don't know how to copy to a non-DAVFile resource.
146
# We may need some more API in IDAVResource.
147
# So far, we need: .exists(), .fp.parent()
150
if not isinstance(destination, twisted.web2.dav.static.DAVFile):
151
log.err("DAV copy between non-DAVFile DAV resources isn't implemented")
152
raise HTTPError(StatusResponse(
153
responsecode.NOT_IMPLEMENTED,
154
"Destination %s is not a DAVFile resource." % (destination_uri,)
158
# Check for existing destination resource
161
overwrite = request.headers.getHeader("overwrite", True)
163
if destination.exists() and not overwrite:
164
log.err("Attempt to %s onto existing file without overwrite flag enabled: %s"
165
% (request.method, destination.fp.path))
166
raise HTTPError(StatusResponse(
167
responsecode.PRECONDITION_FAILED,
168
"Destination %s already exists." % (destination_uri,)
172
# Make sure destination's parent exists
175
if not destination.fp.parent().isdir():
176
log.err("Attempt to %s to a resource with no parent: %s"
177
% (request.method, destination.fp.path))
178
raise HTTPError(StatusResponse(responsecode.CONFLICT, "No parent collection."))
180
return destination, destination_uri, depth