Statistics
| Branch: | Tag: | Revision:

root / pithos / api / functions.py @ 038f1ae9

History | View | Annotate | Download (33.4 kB)

1 5635f9ef Antony Chazapis
# Copyright 2011 GRNET S.A. All rights reserved.
2 5635f9ef Antony Chazapis
# 
3 5635f9ef Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 5635f9ef Antony Chazapis
# without modification, are permitted provided that the following
5 5635f9ef Antony Chazapis
# conditions are met:
6 5635f9ef Antony Chazapis
# 
7 5635f9ef Antony Chazapis
#   1. Redistributions of source code must retain the above
8 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
9 5635f9ef Antony Chazapis
#      disclaimer.
10 5635f9ef Antony Chazapis
# 
11 5635f9ef Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
13 5635f9ef Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 5635f9ef Antony Chazapis
#      provided with the distribution.
15 5635f9ef Antony Chazapis
# 
16 5635f9ef Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 5635f9ef Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 5635f9ef Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 5635f9ef Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 5635f9ef Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 5635f9ef Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 5635f9ef Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 5635f9ef Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 5635f9ef Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 5635f9ef Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 5635f9ef Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 5635f9ef Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 5635f9ef Antony Chazapis
# 
29 5635f9ef Antony Chazapis
# The views and conclusions contained in the software and
30 5635f9ef Antony Chazapis
# documentation are those of the authors and should not be
31 5635f9ef Antony Chazapis
# interpreted as representing official policies, either expressed
32 5635f9ef Antony Chazapis
# or implied, of GRNET S.A.
33 5635f9ef Antony Chazapis
34 b956618e Antony Chazapis
import os
35 b956618e Antony Chazapis
import logging
36 b956618e Antony Chazapis
import hashlib
37 b956618e Antony Chazapis
38 486b2dc2 Giorgos Verigakis
from django.conf import settings
39 b956618e Antony Chazapis
from django.http import HttpResponse
40 b956618e Antony Chazapis
from django.template.loader import render_to_string
41 b956618e Antony Chazapis
from django.utils import simplejson as json
42 b956618e Antony Chazapis
from django.utils.http import parse_etags
43 b956618e Antony Chazapis
44 b956618e Antony Chazapis
from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, ItemNotFound, Conflict,
45 b956618e Antony Chazapis
    LengthRequired, PreconditionFailed, RangeNotSatisfiable, UnprocessableEntity)
46 02c0c3fa Antony Chazapis
from pithos.api.util import (format_header_key, printable_header_dict, get_account_headers,
47 02c0c3fa Antony Chazapis
    put_account_headers, get_container_headers, put_container_headers, get_object_headers, put_object_headers,
48 e0f916bb Antony Chazapis
    update_manifest_meta, update_sharing_meta, update_public_meta, validate_modification_preconditions,
49 3436eeb0 Antony Chazapis
    validate_matching_preconditions, split_container_object_string, copy_or_move_object,
50 3ab38c43 Antony Chazapis
    get_int_parameter, get_content_length, get_content_range, raw_input_socket,
51 3436eeb0 Antony Chazapis
    socket_read_iterator, object_data_response, put_object_block, hashmap_hash, api_method)
52 b956618e Antony Chazapis
from pithos.backends import backend
53 cca6c617 Antony Chazapis
from pithos.backends.base import NotAllowedError
54 b956618e Antony Chazapis
55 b956618e Antony Chazapis
56 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
57 b956618e Antony Chazapis
58 b956618e Antony Chazapis
59 b956618e Antony Chazapis
def top_demux(request):
60 b956618e Antony Chazapis
    if request.method == 'GET':
61 b956618e Antony Chazapis
        return authenticate(request)
62 b956618e Antony Chazapis
    else:
63 b956618e Antony Chazapis
        return method_not_allowed(request)
64 b956618e Antony Chazapis
65 b956618e Antony Chazapis
def account_demux(request, v_account):
66 b956618e Antony Chazapis
    if request.method == 'HEAD':
67 b956618e Antony Chazapis
        return account_meta(request, v_account)
68 b956618e Antony Chazapis
    elif request.method == 'POST':
69 b956618e Antony Chazapis
        return account_update(request, v_account)
70 83dd59c5 Antony Chazapis
    elif request.method == 'GET':
71 83dd59c5 Antony Chazapis
        return container_list(request, v_account)
72 b956618e Antony Chazapis
    else:
73 b956618e Antony Chazapis
        return method_not_allowed(request)
74 b956618e Antony Chazapis
75 b956618e Antony Chazapis
def container_demux(request, v_account, v_container):
76 b956618e Antony Chazapis
    if request.method == 'HEAD':
77 b956618e Antony Chazapis
        return container_meta(request, v_account, v_container)
78 b956618e Antony Chazapis
    elif request.method == 'PUT':
79 b956618e Antony Chazapis
        return container_create(request, v_account, v_container)
80 b956618e Antony Chazapis
    elif request.method == 'POST':
81 b956618e Antony Chazapis
        return container_update(request, v_account, v_container)
82 b956618e Antony Chazapis
    elif request.method == 'DELETE':
83 b956618e Antony Chazapis
        return container_delete(request, v_account, v_container)
84 83dd59c5 Antony Chazapis
    elif request.method == 'GET':
85 83dd59c5 Antony Chazapis
        return object_list(request, v_account, v_container)
86 b956618e Antony Chazapis
    else:
87 b956618e Antony Chazapis
        return method_not_allowed(request)
88 b956618e Antony Chazapis
89 b956618e Antony Chazapis
def object_demux(request, v_account, v_container, v_object):
90 b956618e Antony Chazapis
    if request.method == 'HEAD':
91 b956618e Antony Chazapis
        return object_meta(request, v_account, v_container, v_object)
92 b956618e Antony Chazapis
    elif request.method == 'GET':
93 b956618e Antony Chazapis
        return object_read(request, v_account, v_container, v_object)
94 b956618e Antony Chazapis
    elif request.method == 'PUT':
95 b956618e Antony Chazapis
        return object_write(request, v_account, v_container, v_object)
96 b956618e Antony Chazapis
    elif request.method == 'COPY':
97 b956618e Antony Chazapis
        return object_copy(request, v_account, v_container, v_object)
98 b956618e Antony Chazapis
    elif request.method == 'MOVE':
99 b956618e Antony Chazapis
        return object_move(request, v_account, v_container, v_object)
100 b956618e Antony Chazapis
    elif request.method == 'POST':
101 b956618e Antony Chazapis
        return object_update(request, v_account, v_container, v_object)
102 b956618e Antony Chazapis
    elif request.method == 'DELETE':
103 b956618e Antony Chazapis
        return object_delete(request, v_account, v_container, v_object)
104 b956618e Antony Chazapis
    else:
105 b956618e Antony Chazapis
        return method_not_allowed(request)
106 b956618e Antony Chazapis
107 b956618e Antony Chazapis
@api_method('GET')
108 b956618e Antony Chazapis
def authenticate(request):
109 b956618e Antony Chazapis
    # Normal Response Codes: 204
110 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
111 b956618e Antony Chazapis
    #                       unauthorized (401),
112 b956618e Antony Chazapis
    #                       badRequest (400)
113 b956618e Antony Chazapis
    
114 b956618e Antony Chazapis
    x_auth_user = request.META.get('HTTP_X_AUTH_USER')
115 b956618e Antony Chazapis
    x_auth_key = request.META.get('HTTP_X_AUTH_KEY')
116 b956618e Antony Chazapis
    if not x_auth_user or not x_auth_key:
117 b956618e Antony Chazapis
        raise BadRequest('Missing X-Auth-User or X-Auth-Key header')
118 b956618e Antony Chazapis
    response = HttpResponse(status=204)
119 486b2dc2 Giorgos Verigakis
    inv_auth_tokens = dict((v, k) for k, v in settings.AUTH_TOKENS.items())
120 486b2dc2 Giorgos Verigakis
    response['X-Auth-Token'] = inv_auth_tokens.get(x_auth_user, '0000')
121 b956618e Antony Chazapis
    response['X-Storage-Url'] = os.path.join(request.build_absolute_uri(), 'demo')
122 b956618e Antony Chazapis
    return response
123 b956618e Antony Chazapis
124 b956618e Antony Chazapis
@api_method('HEAD')
125 b956618e Antony Chazapis
def account_meta(request, v_account):
126 b956618e Antony Chazapis
    # Normal Response Codes: 204
127 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
128 b956618e Antony Chazapis
    #                       unauthorized (401),
129 b956618e Antony Chazapis
    #                       badRequest (400)
130 b956618e Antony Chazapis
    
131 83dd59c5 Antony Chazapis
    until = get_int_parameter(request, 'until')
132 cca6c617 Antony Chazapis
    try:
133 cca6c617 Antony Chazapis
        meta = backend.get_account_meta(request.user, v_account, until)
134 02c0c3fa Antony Chazapis
        groups = backend.get_account_groups(request.user, v_account)
135 cca6c617 Antony Chazapis
    except NotAllowedError:
136 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
137 b956618e Antony Chazapis
    
138 b956618e Antony Chazapis
    response = HttpResponse(status=204)
139 02c0c3fa Antony Chazapis
    put_account_headers(response, meta, groups)
140 b956618e Antony Chazapis
    return response
141 b956618e Antony Chazapis
142 b956618e Antony Chazapis
@api_method('POST')
143 b956618e Antony Chazapis
def account_update(request, v_account):
144 b956618e Antony Chazapis
    # Normal Response Codes: 202
145 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
146 b956618e Antony Chazapis
    #                       unauthorized (401),
147 b956618e Antony Chazapis
    #                       badRequest (400)
148 b956618e Antony Chazapis
    
149 02c0c3fa Antony Chazapis
    meta, groups = get_account_headers(request)
150 a6eb13e9 Antony Chazapis
    replace = True
151 a6eb13e9 Antony Chazapis
    if 'update' in request.GET:
152 02c0c3fa Antony Chazapis
        replace = False    
153 02c0c3fa Antony Chazapis
    if groups:
154 02c0c3fa Antony Chazapis
        try:
155 02c0c3fa Antony Chazapis
            backend.update_account_groups(request.user, v_account, groups, replace)
156 02c0c3fa Antony Chazapis
        except NotAllowedError:
157 02c0c3fa Antony Chazapis
            raise Unauthorized('Access denied')
158 02c0c3fa Antony Chazapis
        except ValueError:
159 02c0c3fa Antony Chazapis
            raise BadRequest('Invalid groups header')
160 cca6c617 Antony Chazapis
    try:
161 cca6c617 Antony Chazapis
        backend.update_account_meta(request.user, v_account, meta, replace)
162 cca6c617 Antony Chazapis
    except NotAllowedError:
163 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
164 b956618e Antony Chazapis
    return HttpResponse(status=202)
165 b956618e Antony Chazapis
166 b956618e Antony Chazapis
@api_method('GET', format_allowed=True)
167 b956618e Antony Chazapis
def container_list(request, v_account):
168 b956618e Antony Chazapis
    # Normal Response Codes: 200, 204
169 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
170 b956618e Antony Chazapis
    #                       itemNotFound (404),
171 b956618e Antony Chazapis
    #                       unauthorized (401),
172 b956618e Antony Chazapis
    #                       badRequest (400)
173 b956618e Antony Chazapis
    
174 83dd59c5 Antony Chazapis
    until = get_int_parameter(request, 'until')
175 cca6c617 Antony Chazapis
    try:
176 cca6c617 Antony Chazapis
        meta = backend.get_account_meta(request.user, v_account, until)
177 02c0c3fa Antony Chazapis
        groups = backend.get_account_groups(request.user, v_account)
178 cca6c617 Antony Chazapis
    except NotAllowedError:
179 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
180 b956618e Antony Chazapis
    
181 b956618e Antony Chazapis
    validate_modification_preconditions(request, meta)
182 b956618e Antony Chazapis
    
183 b956618e Antony Chazapis
    response = HttpResponse()
184 02c0c3fa Antony Chazapis
    put_account_headers(response, meta, groups)
185 b956618e Antony Chazapis
    
186 b956618e Antony Chazapis
    marker = request.GET.get('marker')
187 b956618e Antony Chazapis
    limit = request.GET.get('limit')
188 b956618e Antony Chazapis
    if limit:
189 b956618e Antony Chazapis
        try:
190 b956618e Antony Chazapis
            limit = int(limit)
191 b956618e Antony Chazapis
            if limit <= 0:
192 b956618e Antony Chazapis
                raise ValueError
193 b956618e Antony Chazapis
        except ValueError:
194 b956618e Antony Chazapis
            limit = 10000
195 b956618e Antony Chazapis
    
196 b956618e Antony Chazapis
    try:
197 83dd59c5 Antony Chazapis
        containers = backend.list_containers(request.user, v_account, marker, limit, until)
198 cca6c617 Antony Chazapis
    except NotAllowedError:
199 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
200 b956618e Antony Chazapis
    except NameError:
201 b956618e Antony Chazapis
        containers = []
202 b956618e Antony Chazapis
    
203 b956618e Antony Chazapis
    if request.serialization == 'text':
204 b956618e Antony Chazapis
        if len(containers) == 0:
205 b956618e Antony Chazapis
            # The cloudfiles python bindings expect 200 if json/xml.
206 b956618e Antony Chazapis
            response.status_code = 204
207 b956618e Antony Chazapis
            return response
208 b956618e Antony Chazapis
        response.status_code = 200
209 83dd59c5 Antony Chazapis
        response.content = '\n'.join([x[0] for x in containers]) + '\n'
210 b956618e Antony Chazapis
        return response
211 b956618e Antony Chazapis
    
212 b956618e Antony Chazapis
    container_meta = []
213 b956618e Antony Chazapis
    for x in containers:
214 83dd59c5 Antony Chazapis
        if x[1] is not None:
215 83dd59c5 Antony Chazapis
            try:
216 83dd59c5 Antony Chazapis
                meta = backend.get_container_meta(request.user, v_account, x[0], until)
217 3ab38c43 Antony Chazapis
                policy = backend.get_container_policy(request.user, v_account, x[0])
218 cca6c617 Antony Chazapis
            except NotAllowedError:
219 cca6c617 Antony Chazapis
                raise Unauthorized('Access denied')
220 83dd59c5 Antony Chazapis
            except NameError:
221 83dd59c5 Antony Chazapis
                pass
222 038f1ae9 Antony Chazapis
            else:
223 038f1ae9 Antony Chazapis
                for k, v in policy.iteritems():
224 038f1ae9 Antony Chazapis
                    meta['X-Container-Policy-' + k] = v
225 038f1ae9 Antony Chazapis
                container_meta.append(printable_header_dict(meta))
226 b956618e Antony Chazapis
    if request.serialization == 'xml':
227 2c22e4ac Antony Chazapis
        data = render_to_string('containers.xml', {'account': v_account, 'containers': container_meta})
228 b956618e Antony Chazapis
    elif request.serialization  == 'json':
229 b956618e Antony Chazapis
        data = json.dumps(container_meta)
230 b956618e Antony Chazapis
    response.status_code = 200
231 b956618e Antony Chazapis
    response.content = data
232 b956618e Antony Chazapis
    return response
233 b956618e Antony Chazapis
234 b956618e Antony Chazapis
@api_method('HEAD')
235 b956618e Antony Chazapis
def container_meta(request, v_account, v_container):
236 b956618e Antony Chazapis
    # Normal Response Codes: 204
237 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
238 b956618e Antony Chazapis
    #                       itemNotFound (404),
239 b956618e Antony Chazapis
    #                       unauthorized (401),
240 b956618e Antony Chazapis
    #                       badRequest (400)
241 b956618e Antony Chazapis
    
242 83dd59c5 Antony Chazapis
    until = get_int_parameter(request, 'until')
243 b956618e Antony Chazapis
    try:
244 83dd59c5 Antony Chazapis
        meta = backend.get_container_meta(request.user, v_account, v_container, until)
245 83dd59c5 Antony Chazapis
        meta['object_meta'] = backend.list_object_meta(request.user, v_account, v_container, until)
246 3ab38c43 Antony Chazapis
        policy = backend.get_container_policy(request.user, v_account, v_container)
247 cca6c617 Antony Chazapis
    except NotAllowedError:
248 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
249 b956618e Antony Chazapis
    except NameError:
250 b956618e Antony Chazapis
        raise ItemNotFound('Container does not exist')
251 b956618e Antony Chazapis
    
252 b956618e Antony Chazapis
    response = HttpResponse(status=204)
253 3ab38c43 Antony Chazapis
    put_container_headers(response, meta, policy)
254 b956618e Antony Chazapis
    return response
255 b956618e Antony Chazapis
256 b956618e Antony Chazapis
@api_method('PUT')
257 b956618e Antony Chazapis
def container_create(request, v_account, v_container):
258 b956618e Antony Chazapis
    # Normal Response Codes: 201, 202
259 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
260 b956618e Antony Chazapis
    #                       itemNotFound (404),
261 b956618e Antony Chazapis
    #                       unauthorized (401),
262 b956618e Antony Chazapis
    #                       badRequest (400)
263 b956618e Antony Chazapis
    
264 3ab38c43 Antony Chazapis
    meta, policy = get_container_headers(request)
265 b956618e Antony Chazapis
    
266 b956618e Antony Chazapis
    try:
267 3ab38c43 Antony Chazapis
        backend.put_container(request.user, v_account, v_container, policy)
268 b956618e Antony Chazapis
        ret = 201
269 cca6c617 Antony Chazapis
    except NotAllowedError:
270 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
271 b956618e Antony Chazapis
    except NameError:
272 b956618e Antony Chazapis
        ret = 202
273 b956618e Antony Chazapis
    
274 b956618e Antony Chazapis
    if len(meta) > 0:
275 cca6c617 Antony Chazapis
        try:
276 cca6c617 Antony Chazapis
            backend.update_container_meta(request.user, v_account, v_container, meta, replace=True)
277 cca6c617 Antony Chazapis
        except NotAllowedError:
278 cca6c617 Antony Chazapis
            raise Unauthorized('Access denied')
279 cca6c617 Antony Chazapis
        except NameError:
280 cca6c617 Antony Chazapis
            raise ItemNotFound('Container does not exist')
281 b956618e Antony Chazapis
    
282 b956618e Antony Chazapis
    return HttpResponse(status=ret)
283 b956618e Antony Chazapis
284 b956618e Antony Chazapis
@api_method('POST')
285 b956618e Antony Chazapis
def container_update(request, v_account, v_container):
286 b956618e Antony Chazapis
    # Normal Response Codes: 202
287 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
288 b956618e Antony Chazapis
    #                       itemNotFound (404),
289 b956618e Antony Chazapis
    #                       unauthorized (401),
290 b956618e Antony Chazapis
    #                       badRequest (400)
291 b956618e Antony Chazapis
    
292 3ab38c43 Antony Chazapis
    meta, policy = get_container_headers(request)
293 a6eb13e9 Antony Chazapis
    replace = True
294 a6eb13e9 Antony Chazapis
    if 'update' in request.GET:
295 a6eb13e9 Antony Chazapis
        replace = False
296 3ab38c43 Antony Chazapis
    if policy:
297 3ab38c43 Antony Chazapis
        try:
298 3ab38c43 Antony Chazapis
            backend.update_container_policy(request.user, v_account, v_container, policy, replace)
299 3ab38c43 Antony Chazapis
        except NotAllowedError:
300 3ab38c43 Antony Chazapis
            raise Unauthorized('Access denied')
301 3ab38c43 Antony Chazapis
        except NameError:
302 3ab38c43 Antony Chazapis
            raise ItemNotFound('Container does not exist')
303 3ab38c43 Antony Chazapis
        except ValueError:
304 3ab38c43 Antony Chazapis
            raise BadRequest('Invalid policy header')
305 b956618e Antony Chazapis
    try:
306 a6eb13e9 Antony Chazapis
        backend.update_container_meta(request.user, v_account, v_container, meta, replace)
307 cca6c617 Antony Chazapis
    except NotAllowedError:
308 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
309 b956618e Antony Chazapis
    except NameError:
310 b956618e Antony Chazapis
        raise ItemNotFound('Container does not exist')
311 b956618e Antony Chazapis
    return HttpResponse(status=202)
312 b956618e Antony Chazapis
313 b956618e Antony Chazapis
@api_method('DELETE')
314 b956618e Antony Chazapis
def container_delete(request, v_account, v_container):
315 b956618e Antony Chazapis
    # Normal Response Codes: 204
316 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
317 b956618e Antony Chazapis
    #                       conflict (409),
318 b956618e Antony Chazapis
    #                       itemNotFound (404),
319 b956618e Antony Chazapis
    #                       unauthorized (401),
320 b956618e Antony Chazapis
    #                       badRequest (400)
321 b956618e Antony Chazapis
    
322 b956618e Antony Chazapis
    try:
323 83dd59c5 Antony Chazapis
        backend.delete_container(request.user, v_account, v_container)
324 cca6c617 Antony Chazapis
    except NotAllowedError:
325 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
326 b956618e Antony Chazapis
    except NameError:
327 b956618e Antony Chazapis
        raise ItemNotFound('Container does not exist')
328 b956618e Antony Chazapis
    except IndexError:
329 b956618e Antony Chazapis
        raise Conflict('Container is not empty')
330 b956618e Antony Chazapis
    return HttpResponse(status=204)
331 b956618e Antony Chazapis
332 b956618e Antony Chazapis
@api_method('GET', format_allowed=True)
333 b956618e Antony Chazapis
def object_list(request, v_account, v_container):
334 b956618e Antony Chazapis
    # Normal Response Codes: 200, 204
335 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
336 b956618e Antony Chazapis
    #                       itemNotFound (404),
337 b956618e Antony Chazapis
    #                       unauthorized (401),
338 b956618e Antony Chazapis
    #                       badRequest (400)
339 b956618e Antony Chazapis
    
340 83dd59c5 Antony Chazapis
    until = get_int_parameter(request, 'until')
341 b956618e Antony Chazapis
    try:
342 83dd59c5 Antony Chazapis
        meta = backend.get_container_meta(request.user, v_account, v_container, until)
343 83dd59c5 Antony Chazapis
        meta['object_meta'] = backend.list_object_meta(request.user, v_account, v_container, until)
344 3ab38c43 Antony Chazapis
        policy = backend.get_container_policy(request.user, v_account, v_container)
345 cca6c617 Antony Chazapis
    except NotAllowedError:
346 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
347 b956618e Antony Chazapis
    except NameError:
348 b956618e Antony Chazapis
        raise ItemNotFound('Container does not exist')
349 b956618e Antony Chazapis
    
350 b956618e Antony Chazapis
    validate_modification_preconditions(request, meta)
351 b956618e Antony Chazapis
    
352 b956618e Antony Chazapis
    response = HttpResponse()
353 3ab38c43 Antony Chazapis
    put_container_headers(response, meta, policy)
354 b956618e Antony Chazapis
    
355 b956618e Antony Chazapis
    path = request.GET.get('path')
356 b956618e Antony Chazapis
    prefix = request.GET.get('prefix')
357 b956618e Antony Chazapis
    delimiter = request.GET.get('delimiter')
358 b956618e Antony Chazapis
    
359 b956618e Antony Chazapis
    # Path overrides prefix and delimiter.
360 b956618e Antony Chazapis
    virtual = True
361 b956618e Antony Chazapis
    if path:
362 b956618e Antony Chazapis
        prefix = path
363 b956618e Antony Chazapis
        delimiter = '/'
364 b956618e Antony Chazapis
        virtual = False
365 b956618e Antony Chazapis
    
366 b956618e Antony Chazapis
    # Naming policy.
367 b956618e Antony Chazapis
    if prefix and delimiter:
368 b956618e Antony Chazapis
        prefix = prefix + delimiter
369 b956618e Antony Chazapis
    if not prefix:
370 b956618e Antony Chazapis
        prefix = ''
371 b956618e Antony Chazapis
    prefix = prefix.lstrip('/')
372 b956618e Antony Chazapis
    
373 b956618e Antony Chazapis
    marker = request.GET.get('marker')
374 b956618e Antony Chazapis
    limit = request.GET.get('limit')
375 b956618e Antony Chazapis
    if limit:
376 b956618e Antony Chazapis
        try:
377 b956618e Antony Chazapis
            limit = int(limit)
378 b956618e Antony Chazapis
            if limit <= 0:
379 b956618e Antony Chazapis
                raise ValueError
380 b956618e Antony Chazapis
        except ValueError:
381 b956618e Antony Chazapis
            limit = 10000
382 b956618e Antony Chazapis
    
383 22dab079 Antony Chazapis
    keys = request.GET.get('meta')
384 22dab079 Antony Chazapis
    if keys:
385 22dab079 Antony Chazapis
        keys = keys.split(',')
386 02c0c3fa Antony Chazapis
        keys = [format_header_key('X-Object-Meta-' + x.strip()) for x in keys if x.strip() != '']
387 22dab079 Antony Chazapis
    else:
388 22dab079 Antony Chazapis
        keys = []
389 22dab079 Antony Chazapis
    
390 b956618e Antony Chazapis
    try:
391 83dd59c5 Antony Chazapis
        objects = backend.list_objects(request.user, v_account, v_container, prefix, delimiter, marker, limit, virtual, keys, until)
392 cca6c617 Antony Chazapis
    except NotAllowedError:
393 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
394 b956618e Antony Chazapis
    except NameError:
395 b956618e Antony Chazapis
        raise ItemNotFound('Container does not exist')
396 b956618e Antony Chazapis
    
397 b956618e Antony Chazapis
    if request.serialization == 'text':
398 b956618e Antony Chazapis
        if len(objects) == 0:
399 b956618e Antony Chazapis
            # The cloudfiles python bindings expect 200 if json/xml.
400 b956618e Antony Chazapis
            response.status_code = 204
401 b956618e Antony Chazapis
            return response
402 b956618e Antony Chazapis
        response.status_code = 200
403 83dd59c5 Antony Chazapis
        response.content = '\n'.join([x[0] for x in objects]) + '\n'
404 b956618e Antony Chazapis
        return response
405 b956618e Antony Chazapis
    
406 b956618e Antony Chazapis
    object_meta = []
407 b956618e Antony Chazapis
    for x in objects:
408 83dd59c5 Antony Chazapis
        if x[1] is None:
409 b956618e Antony Chazapis
            # Virtual objects/directories.
410 83dd59c5 Antony Chazapis
            object_meta.append({'subdir': x[0]})
411 83dd59c5 Antony Chazapis
        else:
412 83dd59c5 Antony Chazapis
            try:
413 83dd59c5 Antony Chazapis
                meta = backend.get_object_meta(request.user, v_account, v_container, x[0], x[1])
414 e8886082 Antony Chazapis
                if until is None:
415 e8886082 Antony Chazapis
                    permissions = backend.get_object_permissions(request.user, v_account, v_container, x[0])
416 e0f916bb Antony Chazapis
                    public = backend.get_object_public(request.user, v_account, v_container, x[0])
417 e8886082 Antony Chazapis
                else:
418 e8886082 Antony Chazapis
                    permissions = None
419 e0f916bb Antony Chazapis
                    public = None
420 cca6c617 Antony Chazapis
            except NotAllowedError:
421 cca6c617 Antony Chazapis
                raise Unauthorized('Access denied')
422 83dd59c5 Antony Chazapis
            except NameError:
423 83dd59c5 Antony Chazapis
                pass
424 038f1ae9 Antony Chazapis
            else:
425 038f1ae9 Antony Chazapis
                update_sharing_meta(permissions, v_account, v_container, x[0], meta)
426 038f1ae9 Antony Chazapis
                update_public_meta(public, meta)
427 038f1ae9 Antony Chazapis
                object_meta.append(printable_header_dict(meta))
428 b956618e Antony Chazapis
    if request.serialization == 'xml':
429 b956618e Antony Chazapis
        data = render_to_string('objects.xml', {'container': v_container, 'objects': object_meta})
430 b956618e Antony Chazapis
    elif request.serialization  == 'json':
431 b956618e Antony Chazapis
        data = json.dumps(object_meta)
432 b956618e Antony Chazapis
    response.status_code = 200
433 b956618e Antony Chazapis
    response.content = data
434 b956618e Antony Chazapis
    return response
435 b956618e Antony Chazapis
436 b956618e Antony Chazapis
@api_method('HEAD')
437 b956618e Antony Chazapis
def object_meta(request, v_account, v_container, v_object):
438 b956618e Antony Chazapis
    # Normal Response Codes: 204
439 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
440 b956618e Antony Chazapis
    #                       itemNotFound (404),
441 b956618e Antony Chazapis
    #                       unauthorized (401),
442 b956618e Antony Chazapis
    #                       badRequest (400)
443 b956618e Antony Chazapis
    
444 104626e3 Antony Chazapis
    version = request.GET.get('version')
445 b956618e Antony Chazapis
    try:
446 83dd59c5 Antony Chazapis
        meta = backend.get_object_meta(request.user, v_account, v_container, v_object, version)
447 e8886082 Antony Chazapis
        if version is None:
448 e8886082 Antony Chazapis
            permissions = backend.get_object_permissions(request.user, v_account, v_container, v_object)
449 e0f916bb Antony Chazapis
            public = backend.get_object_public(request.user, v_account, v_container, v_object)
450 e8886082 Antony Chazapis
        else:
451 e8886082 Antony Chazapis
            permissions = None
452 e0f916bb Antony Chazapis
            public = None
453 cca6c617 Antony Chazapis
    except NotAllowedError:
454 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
455 b956618e Antony Chazapis
    except NameError:
456 b956618e Antony Chazapis
        raise ItemNotFound('Object does not exist')
457 58a6c894 Antony Chazapis
    except IndexError:
458 58a6c894 Antony Chazapis
        raise ItemNotFound('Version does not exist')
459 b956618e Antony Chazapis
    
460 8cb45c13 Antony Chazapis
    update_manifest_meta(request, v_account, meta)
461 cca6c617 Antony Chazapis
    update_sharing_meta(permissions, v_account, v_container, v_object, meta)
462 e0f916bb Antony Chazapis
    update_public_meta(public, meta)
463 8cb45c13 Antony Chazapis
    
464 cb146cf9 Antony Chazapis
    response = HttpResponse(status=200)
465 02c0c3fa Antony Chazapis
    put_object_headers(response, meta)
466 b956618e Antony Chazapis
    return response
467 b956618e Antony Chazapis
468 22dab079 Antony Chazapis
@api_method('GET', format_allowed=True)
469 b956618e Antony Chazapis
def object_read(request, v_account, v_container, v_object):
470 b956618e Antony Chazapis
    # Normal Response Codes: 200, 206
471 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
472 b956618e Antony Chazapis
    #                       rangeNotSatisfiable (416),
473 b956618e Antony Chazapis
    #                       preconditionFailed (412),
474 b956618e Antony Chazapis
    #                       itemNotFound (404),
475 b956618e Antony Chazapis
    #                       unauthorized (401),
476 b956618e Antony Chazapis
    #                       badRequest (400),
477 b956618e Antony Chazapis
    #                       notModified (304)
478 b956618e Antony Chazapis
    
479 104626e3 Antony Chazapis
    version = request.GET.get('version')
480 e8886082 Antony Chazapis
    
481 e8886082 Antony Chazapis
    # Reply with the version list. Do this first, as the object may be deleted.
482 104626e3 Antony Chazapis
    if version == 'list':
483 e8886082 Antony Chazapis
        if request.serialization == 'text':
484 e8886082 Antony Chazapis
            raise BadRequest('No format specified for version list.')
485 e8886082 Antony Chazapis
        
486 cca6c617 Antony Chazapis
        try:
487 cca6c617 Antony Chazapis
            v = backend.list_versions(request.user, v_account, v_container, v_object)
488 cca6c617 Antony Chazapis
        except NotAllowedError:
489 cca6c617 Antony Chazapis
            raise Unauthorized('Access denied')
490 cca6c617 Antony Chazapis
        d = {'versions': v}
491 e8886082 Antony Chazapis
        if request.serialization == 'xml':
492 e8886082 Antony Chazapis
            d['object'] = v_object
493 e8886082 Antony Chazapis
            data = render_to_string('versions.xml', d)
494 e8886082 Antony Chazapis
        elif request.serialization  == 'json':
495 e8886082 Antony Chazapis
            data = json.dumps(d)
496 e8886082 Antony Chazapis
        
497 e8886082 Antony Chazapis
        response = HttpResponse(data, status=200)
498 e8886082 Antony Chazapis
        response['Content-Length'] = len(data)
499 e8886082 Antony Chazapis
        return response
500 e8886082 Antony Chazapis
    
501 b956618e Antony Chazapis
    try:
502 83dd59c5 Antony Chazapis
        meta = backend.get_object_meta(request.user, v_account, v_container, v_object, version)
503 e8886082 Antony Chazapis
        if version is None:
504 e8886082 Antony Chazapis
            permissions = backend.get_object_permissions(request.user, v_account, v_container, v_object)
505 307915f1 Antony Chazapis
            public = backend.get_object_public(request.user, v_account, v_container, v_object)
506 e8886082 Antony Chazapis
        else:
507 e8886082 Antony Chazapis
            permissions = None
508 e0f916bb Antony Chazapis
            public = None
509 cca6c617 Antony Chazapis
    except NotAllowedError:
510 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
511 b956618e Antony Chazapis
    except NameError:
512 b956618e Antony Chazapis
        raise ItemNotFound('Object does not exist')
513 58a6c894 Antony Chazapis
    except IndexError:
514 58a6c894 Antony Chazapis
        raise ItemNotFound('Version does not exist')
515 b956618e Antony Chazapis
    
516 8cb45c13 Antony Chazapis
    update_manifest_meta(request, v_account, meta)
517 cca6c617 Antony Chazapis
    update_sharing_meta(permissions, v_account, v_container, v_object, meta)
518 e0f916bb Antony Chazapis
    update_public_meta(public, meta)
519 8cb45c13 Antony Chazapis
    
520 22dab079 Antony Chazapis
    # Evaluate conditions.
521 b956618e Antony Chazapis
    validate_modification_preconditions(request, meta)
522 22dab079 Antony Chazapis
    try:
523 22dab079 Antony Chazapis
        validate_matching_preconditions(request, meta)
524 22dab079 Antony Chazapis
    except NotModified:
525 22dab079 Antony Chazapis
        response = HttpResponse(status=304)
526 22dab079 Antony Chazapis
        response['ETag'] = meta['hash']
527 22dab079 Antony Chazapis
        return response
528 b956618e Antony Chazapis
    
529 8cb45c13 Antony Chazapis
    sizes = []
530 8cb45c13 Antony Chazapis
    hashmaps = []
531 8cb45c13 Antony Chazapis
    if 'X-Object-Manifest' in meta:
532 8cb45c13 Antony Chazapis
        try:
533 6d817842 Antony Chazapis
            src_container, src_name = split_container_object_string('/' + meta['X-Object-Manifest'])
534 8cb45c13 Antony Chazapis
            objects = backend.list_objects(request.user, v_account, src_container, prefix=src_name, virtual=False)
535 cca6c617 Antony Chazapis
        except NotAllowedError:
536 cca6c617 Antony Chazapis
            raise Unauthorized('Access denied')
537 8cb45c13 Antony Chazapis
        except ValueError:
538 8cb45c13 Antony Chazapis
            raise BadRequest('Invalid X-Object-Manifest header')
539 8cb45c13 Antony Chazapis
        except NameError:
540 8cb45c13 Antony Chazapis
            raise ItemNotFound('Container does not exist')
541 8cb45c13 Antony Chazapis
        
542 8cb45c13 Antony Chazapis
        try:
543 8cb45c13 Antony Chazapis
            for x in objects:
544 8cb45c13 Antony Chazapis
                s, h = backend.get_object_hashmap(request.user, v_account, src_container, x[0], x[1])
545 8cb45c13 Antony Chazapis
                sizes.append(s)
546 8cb45c13 Antony Chazapis
                hashmaps.append(h)
547 cca6c617 Antony Chazapis
        except NotAllowedError:
548 cca6c617 Antony Chazapis
            raise Unauthorized('Access denied')
549 8cb45c13 Antony Chazapis
        except NameError:
550 8cb45c13 Antony Chazapis
            raise ItemNotFound('Object does not exist')
551 8cb45c13 Antony Chazapis
        except IndexError:
552 8cb45c13 Antony Chazapis
            raise ItemNotFound('Version does not exist')
553 8cb45c13 Antony Chazapis
    else:
554 8cb45c13 Antony Chazapis
        try:
555 8cb45c13 Antony Chazapis
            s, h = backend.get_object_hashmap(request.user, v_account, v_container, v_object, version)
556 8cb45c13 Antony Chazapis
            sizes.append(s)
557 8cb45c13 Antony Chazapis
            hashmaps.append(h)
558 cca6c617 Antony Chazapis
        except NotAllowedError:
559 cca6c617 Antony Chazapis
            raise Unauthorized('Access denied')
560 8cb45c13 Antony Chazapis
        except NameError:
561 8cb45c13 Antony Chazapis
            raise ItemNotFound('Object does not exist')
562 8cb45c13 Antony Chazapis
        except IndexError:
563 8cb45c13 Antony Chazapis
            raise ItemNotFound('Version does not exist')
564 b956618e Antony Chazapis
    
565 22dab079 Antony Chazapis
    # Reply with the hashmap.
566 22dab079 Antony Chazapis
    if request.serialization != 'text':
567 8cb45c13 Antony Chazapis
        size = sum(sizes)
568 8cb45c13 Antony Chazapis
        hashmap = sum(hashmaps, [])
569 6bc372a4 Antony Chazapis
        d = {'block_size': backend.block_size, 'block_hash': backend.hash_algorithm, 'bytes': size, 'hashes': hashmap}
570 22dab079 Antony Chazapis
        if request.serialization == 'xml':
571 6bc372a4 Antony Chazapis
            d['object'] = v_object
572 6bc372a4 Antony Chazapis
            data = render_to_string('hashes.xml', d)
573 22dab079 Antony Chazapis
        elif request.serialization  == 'json':
574 6bc372a4 Antony Chazapis
            data = json.dumps(d)
575 22dab079 Antony Chazapis
        
576 22dab079 Antony Chazapis
        response = HttpResponse(data, status=200)
577 02c0c3fa Antony Chazapis
        put_object_headers(response, meta)
578 22dab079 Antony Chazapis
        response['Content-Length'] = len(data)
579 22dab079 Antony Chazapis
        return response
580 22dab079 Antony Chazapis
    
581 8cb45c13 Antony Chazapis
    return object_data_response(request, sizes, hashmaps, meta)
582 b956618e Antony Chazapis
583 76985443 Sofia Papagiannaki
@api_method('PUT', format_allowed=True)
584 b956618e Antony Chazapis
def object_write(request, v_account, v_container, v_object):
585 b956618e Antony Chazapis
    # Normal Response Codes: 201
586 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
587 b956618e Antony Chazapis
    #                       unprocessableEntity (422),
588 b956618e Antony Chazapis
    #                       lengthRequired (411),
589 3436eeb0 Antony Chazapis
    #                       conflict (409),
590 b956618e Antony Chazapis
    #                       itemNotFound (404),
591 b956618e Antony Chazapis
    #                       unauthorized (401),
592 b956618e Antony Chazapis
    #                       badRequest (400)
593 b956618e Antony Chazapis
    copy_from = request.META.get('HTTP_X_COPY_FROM')
594 b956618e Antony Chazapis
    move_from = request.META.get('HTTP_X_MOVE_FROM')
595 b956618e Antony Chazapis
    if copy_from or move_from:
596 b956618e Antony Chazapis
        # TODO: Why is this required? Copy this ammount?
597 22dab079 Antony Chazapis
        content_length = get_content_length(request)
598 b956618e Antony Chazapis
        
599 b956618e Antony Chazapis
        if move_from:
600 83dd59c5 Antony Chazapis
            try:
601 83dd59c5 Antony Chazapis
                src_container, src_name = split_container_object_string(move_from)
602 83dd59c5 Antony Chazapis
            except ValueError:
603 83dd59c5 Antony Chazapis
                raise BadRequest('Invalid X-Move-From header')
604 83dd59c5 Antony Chazapis
            copy_or_move_object(request, v_account, src_container, src_name, v_container, v_object, move=True)
605 b956618e Antony Chazapis
        else:
606 83dd59c5 Antony Chazapis
            try:
607 83dd59c5 Antony Chazapis
                src_container, src_name = split_container_object_string(copy_from)
608 83dd59c5 Antony Chazapis
            except ValueError:
609 83dd59c5 Antony Chazapis
                raise BadRequest('Invalid X-Copy-From header')
610 83dd59c5 Antony Chazapis
            copy_or_move_object(request, v_account, src_container, src_name, v_container, v_object, move=False)
611 b956618e Antony Chazapis
        return HttpResponse(status=201)
612 b956618e Antony Chazapis
    
613 3ab38c43 Antony Chazapis
    meta, permissions, public = get_object_headers(request)
614 b956618e Antony Chazapis
    content_length = -1
615 b956618e Antony Chazapis
    if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
616 22dab079 Antony Chazapis
        content_length = get_content_length(request)
617 b956618e Antony Chazapis
    # Should be BadRequest, but API says otherwise.
618 b956618e Antony Chazapis
    if 'Content-Type' not in meta:
619 b956618e Antony Chazapis
        raise LengthRequired('Missing Content-Type header')
620 b956618e Antony Chazapis
    
621 76985443 Sofia Papagiannaki
    if request.serialization == 'json':
622 76985443 Sofia Papagiannaki
        data = ''
623 76985443 Sofia Papagiannaki
        sock = raw_input_socket(request)
624 76985443 Sofia Papagiannaki
        for block in socket_read_iterator(sock, content_length, backend.block_size):
625 76985443 Sofia Papagiannaki
            data = '%s%s' % (data, block)
626 76985443 Sofia Papagiannaki
        d = json.loads(data)
627 76985443 Sofia Papagiannaki
        if not hasattr(d, '__getitem__'):
628 76985443 Sofia Papagiannaki
            raise BadRequest('Invalid data formating')
629 76985443 Sofia Papagiannaki
        try:
630 76985443 Sofia Papagiannaki
            hashmap = d['hashes']
631 76985443 Sofia Papagiannaki
            size = d['bytes']
632 76985443 Sofia Papagiannaki
        except KeyError:
633 76985443 Sofia Papagiannaki
            raise BadRequest('Invalid data formatting')
634 76985443 Sofia Papagiannaki
        meta.update({'hash': hashmap_hash(hashmap)}) # Update ETag.
635 ce2eba7e Sofia Papagiannaki
    elif request.serialization == 'xml':
636 76985443 Sofia Papagiannaki
        #TODO support for xml
637 76985443 Sofia Papagiannaki
        raise BadRequest('Format xml is not supported')
638 76985443 Sofia Papagiannaki
    else:
639 76985443 Sofia Papagiannaki
        md5 = hashlib.md5()
640 76985443 Sofia Papagiannaki
        size = 0
641 76985443 Sofia Papagiannaki
        hashmap = []
642 76985443 Sofia Papagiannaki
        sock = raw_input_socket(request)
643 76985443 Sofia Papagiannaki
        for data in socket_read_iterator(sock, content_length, backend.block_size):
644 76985443 Sofia Papagiannaki
            # TODO: Raise 408 (Request Timeout) if this takes too long.
645 76985443 Sofia Papagiannaki
            # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
646 76985443 Sofia Papagiannaki
            size += len(data)
647 76985443 Sofia Papagiannaki
            hashmap.append(backend.put_block(data))
648 76985443 Sofia Papagiannaki
            md5.update(data)
649 76985443 Sofia Papagiannaki
        
650 76985443 Sofia Papagiannaki
        meta['hash'] = md5.hexdigest().lower()
651 76985443 Sofia Papagiannaki
        etag = request.META.get('HTTP_ETAG')
652 76985443 Sofia Papagiannaki
        if etag and parse_etags(etag)[0].lower() != meta['hash']:
653 76985443 Sofia Papagiannaki
            raise UnprocessableEntity('Object ETag does not match')
654 22dab079 Antony Chazapis
    
655 22dab079 Antony Chazapis
    try:
656 3436eeb0 Antony Chazapis
        backend.update_object_hashmap(request.user, v_account, v_container, v_object, size, hashmap, meta, True, permissions)
657 cca6c617 Antony Chazapis
    except NotAllowedError:
658 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
659 76985443 Sofia Papagiannaki
    except IndexError, e:
660 1993fea9 Antony Chazapis
        raise Conflict(json.dumps(e.data))
661 22dab079 Antony Chazapis
    except NameError:
662 22dab079 Antony Chazapis
        raise ItemNotFound('Container does not exist')
663 3436eeb0 Antony Chazapis
    except ValueError:
664 3436eeb0 Antony Chazapis
        raise BadRequest('Invalid sharing header')
665 1993fea9 Antony Chazapis
    except AttributeError, e:
666 1993fea9 Antony Chazapis
        raise Conflict(json.dumps(e.data))
667 e0f916bb Antony Chazapis
    if public is not None:
668 e0f916bb Antony Chazapis
        try:
669 e0f916bb Antony Chazapis
            backend.update_object_public(request.user, v_account, v_container, v_object, public)
670 e0f916bb Antony Chazapis
        except NotAllowedError:
671 e0f916bb Antony Chazapis
            raise Unauthorized('Access denied')
672 e0f916bb Antony Chazapis
        except NameError:
673 e0f916bb Antony Chazapis
            raise ItemNotFound('Object does not exist')
674 b956618e Antony Chazapis
    
675 1993fea9 Antony Chazapis
    response = HttpResponse(status=201)
676 b956618e Antony Chazapis
    response['ETag'] = meta['hash']
677 b956618e Antony Chazapis
    return response
678 b956618e Antony Chazapis
679 b956618e Antony Chazapis
@api_method('COPY')
680 b956618e Antony Chazapis
def object_copy(request, v_account, v_container, v_object):
681 b956618e Antony Chazapis
    # Normal Response Codes: 201
682 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
683 b956618e Antony Chazapis
    #                       itemNotFound (404),
684 b956618e Antony Chazapis
    #                       unauthorized (401),
685 b956618e Antony Chazapis
    #                       badRequest (400)
686 b956618e Antony Chazapis
    
687 b956618e Antony Chazapis
    dest_path = request.META.get('HTTP_DESTINATION')
688 b956618e Antony Chazapis
    if not dest_path:
689 b956618e Antony Chazapis
        raise BadRequest('Missing Destination header')
690 83dd59c5 Antony Chazapis
    try:
691 83dd59c5 Antony Chazapis
        dest_container, dest_name = split_container_object_string(dest_path)
692 83dd59c5 Antony Chazapis
    except ValueError:
693 83dd59c5 Antony Chazapis
        raise BadRequest('Invalid Destination header')
694 83dd59c5 Antony Chazapis
    copy_or_move_object(request, v_account, v_container, v_object, dest_container, dest_name, move=False)
695 b956618e Antony Chazapis
    return HttpResponse(status=201)
696 b956618e Antony Chazapis
697 b956618e Antony Chazapis
@api_method('MOVE')
698 b956618e Antony Chazapis
def object_move(request, v_account, v_container, v_object):
699 b956618e Antony Chazapis
    # Normal Response Codes: 201
700 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
701 b956618e Antony Chazapis
    #                       itemNotFound (404),
702 b956618e Antony Chazapis
    #                       unauthorized (401),
703 b956618e Antony Chazapis
    #                       badRequest (400)
704 b956618e Antony Chazapis
    
705 b956618e Antony Chazapis
    dest_path = request.META.get('HTTP_DESTINATION')
706 b956618e Antony Chazapis
    if not dest_path:
707 b956618e Antony Chazapis
        raise BadRequest('Missing Destination header')
708 83dd59c5 Antony Chazapis
    try:
709 83dd59c5 Antony Chazapis
        dest_container, dest_name = split_container_object_string(dest_path)
710 83dd59c5 Antony Chazapis
    except ValueError:
711 83dd59c5 Antony Chazapis
        raise BadRequest('Invalid Destination header')
712 83dd59c5 Antony Chazapis
    copy_or_move_object(request, v_account, v_container, v_object, dest_container, dest_name, move=True)
713 b956618e Antony Chazapis
    return HttpResponse(status=201)
714 b956618e Antony Chazapis
715 b956618e Antony Chazapis
@api_method('POST')
716 b956618e Antony Chazapis
def object_update(request, v_account, v_container, v_object):
717 e9285524 Antony Chazapis
    # Normal Response Codes: 202, 204
718 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
719 3436eeb0 Antony Chazapis
    #                       conflict (409),
720 b956618e Antony Chazapis
    #                       itemNotFound (404),
721 b956618e Antony Chazapis
    #                       unauthorized (401),
722 b956618e Antony Chazapis
    #                       badRequest (400)
723 b956618e Antony Chazapis
    
724 3ab38c43 Antony Chazapis
    meta, permissions, public = get_object_headers(request)
725 22dab079 Antony Chazapis
    content_type = meta.get('Content-Type')
726 22dab079 Antony Chazapis
    if content_type:
727 b956618e Antony Chazapis
        del(meta['Content-Type']) # Do not allow changing the Content-Type.
728 22dab079 Antony Chazapis
    
729 cfe6939d Antony Chazapis
    try:
730 83dd59c5 Antony Chazapis
        prev_meta = backend.get_object_meta(request.user, v_account, v_container, v_object)
731 cca6c617 Antony Chazapis
    except NotAllowedError:
732 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
733 cfe6939d Antony Chazapis
    except NameError:
734 cfe6939d Antony Chazapis
        raise ItemNotFound('Object does not exist')
735 ac62f6da Antony Chazapis
    # If replacing, keep previous values of 'Content-Type' and 'hash'.
736 ac62f6da Antony Chazapis
    replace = True
737 ac62f6da Antony Chazapis
    if 'update' in request.GET:
738 ac62f6da Antony Chazapis
        replace = False
739 ac62f6da Antony Chazapis
    if replace:
740 22dab079 Antony Chazapis
        for k in ('Content-Type', 'hash'):
741 22dab079 Antony Chazapis
            if k in prev_meta:
742 22dab079 Antony Chazapis
                meta[k] = prev_meta[k]
743 22dab079 Antony Chazapis
    
744 ac62f6da Antony Chazapis
    # A Content-Type header indicates data updates.
745 ac62f6da Antony Chazapis
    if not content_type or content_type != 'application/octet-stream':
746 cca6c617 Antony Chazapis
        # Do permissions first, as it may fail easier.
747 cca6c617 Antony Chazapis
        if permissions is not None:
748 ac62f6da Antony Chazapis
            try:
749 ac62f6da Antony Chazapis
                backend.update_object_permissions(request.user, v_account, v_container, v_object, permissions)
750 cca6c617 Antony Chazapis
            except NotAllowedError:
751 cca6c617 Antony Chazapis
                raise Unauthorized('Access denied')
752 ac62f6da Antony Chazapis
            except NameError:
753 ac62f6da Antony Chazapis
                raise ItemNotFound('Object does not exist')
754 ac62f6da Antony Chazapis
            except ValueError:
755 ac62f6da Antony Chazapis
                raise BadRequest('Invalid sharing header')
756 1993fea9 Antony Chazapis
            except AttributeError, e:
757 1993fea9 Antony Chazapis
                raise Conflict(json.dumps(e.data))
758 e0f916bb Antony Chazapis
        if public is not None:
759 e0f916bb Antony Chazapis
            try:
760 e0f916bb Antony Chazapis
                backend.update_object_public(request.user, v_account, v_container, v_object, public)
761 e0f916bb Antony Chazapis
            except NotAllowedError:
762 e0f916bb Antony Chazapis
                raise Unauthorized('Access denied')
763 e0f916bb Antony Chazapis
            except NameError:
764 e0f916bb Antony Chazapis
                raise ItemNotFound('Object does not exist')
765 cca6c617 Antony Chazapis
        try:
766 cca6c617 Antony Chazapis
            backend.update_object_meta(request.user, v_account, v_container, v_object, meta, replace)
767 cca6c617 Antony Chazapis
        except NotAllowedError:
768 cca6c617 Antony Chazapis
            raise Unauthorized('Access denied')
769 cca6c617 Antony Chazapis
        except NameError:
770 cca6c617 Antony Chazapis
            raise ItemNotFound('Object does not exist')
771 cfe6939d Antony Chazapis
        return HttpResponse(status=202)
772 ac62f6da Antony Chazapis
    
773 cfe6939d Antony Chazapis
    # Single range update. Range must be in Content-Range.
774 22dab079 Antony Chazapis
    # Based on: http://code.google.com/p/gears/wiki/ContentRangePostProposal
775 cfe6939d Antony Chazapis
    # (with the addition that '*' is allowed for the range - will append).
776 22dab079 Antony Chazapis
    content_range = request.META.get('HTTP_CONTENT_RANGE')
777 22dab079 Antony Chazapis
    if not content_range:
778 ac62f6da Antony Chazapis
        raise BadRequest('Missing Content-Range header')
779 22dab079 Antony Chazapis
    ranges = get_content_range(request)
780 22dab079 Antony Chazapis
    if not ranges:
781 ac62f6da Antony Chazapis
        raise RangeNotSatisfiable('Invalid Content-Range header')
782 cfe6939d Antony Chazapis
    # Require either a Content-Length, or 'chunked' Transfer-Encoding.
783 cfe6939d Antony Chazapis
    content_length = -1
784 cfe6939d Antony Chazapis
    if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
785 cfe6939d Antony Chazapis
        content_length = get_content_length(request)
786 22dab079 Antony Chazapis
    
787 cfe6939d Antony Chazapis
    try:
788 83dd59c5 Antony Chazapis
        size, hashmap = backend.get_object_hashmap(request.user, v_account, v_container, v_object)
789 cca6c617 Antony Chazapis
    except NotAllowedError:
790 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
791 cfe6939d Antony Chazapis
    except NameError:
792 cfe6939d Antony Chazapis
        raise ItemNotFound('Object does not exist')
793 cfe6939d Antony Chazapis
    
794 cfe6939d Antony Chazapis
    offset, length, total = ranges
795 cfe6939d Antony Chazapis
    if offset is None:
796 cfe6939d Antony Chazapis
        offset = size
797 cb146cf9 Antony Chazapis
    elif offset > size:
798 cb146cf9 Antony Chazapis
        raise RangeNotSatisfiable('Supplied offset is beyond object limits')
799 9a7f747a Sofia Papagiannaki
    if length is None or content_length == -1:
800 cfe6939d Antony Chazapis
        length = content_length # Nevermind the error.
801 cfe6939d Antony Chazapis
    elif length != content_length:
802 cfe6939d Antony Chazapis
        raise BadRequest('Content length does not match range length')
803 cfe6939d Antony Chazapis
    if total is not None and (total != size or offset >= size or (length > 0 and offset + length >= size)):
804 cfe6939d Antony Chazapis
        raise RangeNotSatisfiable('Supplied range will change provided object limits')
805 cfe6939d Antony Chazapis
    
806 cfe6939d Antony Chazapis
    sock = raw_input_socket(request)
807 cfe6939d Antony Chazapis
    data = ''
808 cfe6939d Antony Chazapis
    for d in socket_read_iterator(sock, length, backend.block_size):
809 cfe6939d Antony Chazapis
        # TODO: Raise 408 (Request Timeout) if this takes too long.
810 cfe6939d Antony Chazapis
        # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
811 cfe6939d Antony Chazapis
        data += d
812 cb146cf9 Antony Chazapis
        bytes = put_object_block(hashmap, data, offset)
813 cb146cf9 Antony Chazapis
        offset += bytes
814 cb146cf9 Antony Chazapis
        data = data[bytes:]
815 cfe6939d Antony Chazapis
    if len(data) > 0:
816 cb146cf9 Antony Chazapis
        put_object_block(hashmap, data, offset)
817 cfe6939d Antony Chazapis
    
818 cfe6939d Antony Chazapis
    if offset > size:
819 cfe6939d Antony Chazapis
        size = offset
820 ac62f6da Antony Chazapis
    meta.update({'hash': hashmap_hash(hashmap)}) # Update ETag.
821 cfe6939d Antony Chazapis
    try:
822 ac62f6da Antony Chazapis
        backend.update_object_hashmap(request.user, v_account, v_container, v_object, size, hashmap, meta, replace, permissions)
823 cca6c617 Antony Chazapis
    except NotAllowedError:
824 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
825 cfe6939d Antony Chazapis
    except NameError:
826 cfe6939d Antony Chazapis
        raise ItemNotFound('Container does not exist')
827 3436eeb0 Antony Chazapis
    except ValueError:
828 3436eeb0 Antony Chazapis
        raise BadRequest('Invalid sharing header')
829 1993fea9 Antony Chazapis
    except AttributeError, e:
830 1993fea9 Antony Chazapis
        raise Conflict(json.dumps(e.data))
831 e0f916bb Antony Chazapis
    if public is not None:
832 e0f916bb Antony Chazapis
        try:
833 e0f916bb Antony Chazapis
            backend.update_object_public(request.user, v_account, v_container, v_object, public)
834 e0f916bb Antony Chazapis
        except NotAllowedError:
835 e0f916bb Antony Chazapis
            raise Unauthorized('Access denied')
836 e0f916bb Antony Chazapis
        except NameError:
837 e0f916bb Antony Chazapis
            raise ItemNotFound('Object does not exist')
838 3436eeb0 Antony Chazapis
    
839 e9285524 Antony Chazapis
    response = HttpResponse(status=204)
840 e9285524 Antony Chazapis
    response['ETag'] = meta['hash']
841 e9285524 Antony Chazapis
    return response
842 b956618e Antony Chazapis
843 b956618e Antony Chazapis
@api_method('DELETE')
844 b956618e Antony Chazapis
def object_delete(request, v_account, v_container, v_object):
845 b956618e Antony Chazapis
    # Normal Response Codes: 204
846 b956618e Antony Chazapis
    # Error Response Codes: serviceUnavailable (503),
847 b956618e Antony Chazapis
    #                       itemNotFound (404),
848 b956618e Antony Chazapis
    #                       unauthorized (401),
849 b956618e Antony Chazapis
    #                       badRequest (400)
850 b956618e Antony Chazapis
    
851 b956618e Antony Chazapis
    try:
852 83dd59c5 Antony Chazapis
        backend.delete_object(request.user, v_account, v_container, v_object)
853 cca6c617 Antony Chazapis
    except NotAllowedError:
854 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
855 b956618e Antony Chazapis
    except NameError:
856 b956618e Antony Chazapis
        raise ItemNotFound('Object does not exist')
857 b956618e Antony Chazapis
    return HttpResponse(status=204)
858 b956618e Antony Chazapis
859 b956618e Antony Chazapis
@api_method()
860 b956618e Antony Chazapis
def method_not_allowed(request):
861 b956618e Antony Chazapis
    raise BadRequest('Method not allowed')