Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / functions.py @ 64b6bc45

History | View | Annotate | Download (50.5 kB)

1 2e662088 Antony Chazapis
# Copyright 2011-2012 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 a7dff008 Antony Chazapis
from xml.dom import minidom
35 64b6bc45 root
from urllib import unquote
36 b956618e Antony Chazapis
37 486b2dc2 Giorgos Verigakis
from django.conf import settings
38 b956618e Antony Chazapis
from django.http import HttpResponse
39 b956618e Antony Chazapis
from django.template.loader import render_to_string
40 b956618e Antony Chazapis
from django.utils import simplejson as json
41 b956618e Antony Chazapis
from django.utils.http import parse_etags
42 88283e9e Antony Chazapis
from django.utils.encoding import smart_str
43 8fbfd37b Antony Chazapis
from django.views.decorators.csrf import csrf_exempt
44 b956618e Antony Chazapis
45 6e147ecc Antony Chazapis
from synnefo.lib.astakos import get_user
46 1713c946 Antony Chazapis
47 297513ba Antony Chazapis
from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound, Conflict,
48 5df6c6d1 Antony Chazapis
    LengthRequired, PreconditionFailed, RequestEntityTooLarge, RangeNotSatisfiable, UnprocessableEntity)
49 6b6b6c1e Antony Chazapis
from pithos.api.util import (json_encode_decimal, rename_meta_key, format_header_key, printable_header_dict,
50 6b6b6c1e Antony Chazapis
    get_account_headers, put_account_headers, get_container_headers, put_container_headers, get_object_headers,
51 6b6b6c1e Antony Chazapis
    put_object_headers, update_manifest_meta, update_sharing_meta, update_public_meta,
52 6b6b6c1e Antony Chazapis
    validate_modification_preconditions, validate_matching_preconditions, split_container_object_string,
53 6b6b6c1e Antony Chazapis
    copy_or_move_object, get_int_parameter, get_content_length, get_content_range, socket_read_iterator,
54 af7bb62f Antony Chazapis
    SaveToBackendHandler, object_data_response, put_object_block, hashmap_md5, simple_list_response, api_method)
55 ce2a2ef0 Antony Chazapis
from pithos.api.settings import AUTHENTICATION_URL, AUTHENTICATION_USERS, COOKIE_NAME
56 b956618e Antony Chazapis
57 6e147ecc Antony Chazapis
from pithos.backends.base import NotAllowedError, QuotaError
58 6e147ecc Antony Chazapis
from pithos.backends.filter import parse_filters
59 6e147ecc Antony Chazapis
60 a7dff008 Antony Chazapis
import logging
61 a7dff008 Antony Chazapis
import hashlib
62 a7dff008 Antony Chazapis
63 b956618e Antony Chazapis
64 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
65 b956618e Antony Chazapis
66 b956618e Antony Chazapis
67 8fbfd37b Antony Chazapis
@csrf_exempt
68 b956618e Antony Chazapis
def top_demux(request):
69 cf441bae Antony Chazapis
    get_user(request, AUTHENTICATION_URL, AUTHENTICATION_USERS)
70 b956618e Antony Chazapis
    if request.method == 'GET':
71 a70c5269 Antony Chazapis
        if getattr(request, 'user', None) is not None:
72 f6c97079 Antony Chazapis
            return account_list(request)
73 b956618e Antony Chazapis
        return authenticate(request)
74 b956618e Antony Chazapis
    else:
75 b956618e Antony Chazapis
        return method_not_allowed(request)
76 b956618e Antony Chazapis
77 8fbfd37b Antony Chazapis
@csrf_exempt
78 b956618e Antony Chazapis
def account_demux(request, v_account):
79 cf441bae Antony Chazapis
    get_user(request, AUTHENTICATION_URL, AUTHENTICATION_USERS)
80 b956618e Antony Chazapis
    if request.method == 'HEAD':
81 b956618e Antony Chazapis
        return account_meta(request, v_account)
82 b956618e Antony Chazapis
    elif request.method == 'POST':
83 b956618e Antony Chazapis
        return account_update(request, v_account)
84 83dd59c5 Antony Chazapis
    elif request.method == 'GET':
85 83dd59c5 Antony Chazapis
        return container_list(request, v_account)
86 b956618e Antony Chazapis
    else:
87 b956618e Antony Chazapis
        return method_not_allowed(request)
88 b956618e Antony Chazapis
89 8fbfd37b Antony Chazapis
@csrf_exempt
90 b956618e Antony Chazapis
def container_demux(request, v_account, v_container):
91 cf441bae Antony Chazapis
    get_user(request, AUTHENTICATION_URL, AUTHENTICATION_USERS)
92 b956618e Antony Chazapis
    if request.method == 'HEAD':
93 b956618e Antony Chazapis
        return container_meta(request, v_account, v_container)
94 b956618e Antony Chazapis
    elif request.method == 'PUT':
95 b956618e Antony Chazapis
        return container_create(request, v_account, v_container)
96 b956618e Antony Chazapis
    elif request.method == 'POST':
97 b956618e Antony Chazapis
        return container_update(request, v_account, v_container)
98 b956618e Antony Chazapis
    elif request.method == 'DELETE':
99 b956618e Antony Chazapis
        return container_delete(request, v_account, v_container)
100 83dd59c5 Antony Chazapis
    elif request.method == 'GET':
101 83dd59c5 Antony Chazapis
        return object_list(request, v_account, v_container)
102 b956618e Antony Chazapis
    else:
103 b956618e Antony Chazapis
        return method_not_allowed(request)
104 b956618e Antony Chazapis
105 8fbfd37b Antony Chazapis
@csrf_exempt
106 b956618e Antony Chazapis
def object_demux(request, v_account, v_container, v_object):
107 ce2a2ef0 Antony Chazapis
    # Helper to avoid placing the token in the URL when loading objects from a browser.
108 ce2a2ef0 Antony Chazapis
    token = None
109 ce2a2ef0 Antony Chazapis
    if request.method in ('HEAD', 'GET') and COOKIE_NAME in request.COOKIES:
110 64b6bc45 root
        cookie_value = unquote(request.COOKIES.get(COOKIE_NAME, ''))
111 ce2a2ef0 Antony Chazapis
        if cookie_value and '|' in cookie_value:
112 ce2a2ef0 Antony Chazapis
            token = cookie_value.split('|', 1)[1]
113 ce2a2ef0 Antony Chazapis
    get_user(request, AUTHENTICATION_URL, AUTHENTICATION_USERS, token)
114 b956618e Antony Chazapis
    if request.method == 'HEAD':
115 b956618e Antony Chazapis
        return object_meta(request, v_account, v_container, v_object)
116 b956618e Antony Chazapis
    elif request.method == 'GET':
117 b956618e Antony Chazapis
        return object_read(request, v_account, v_container, v_object)
118 b956618e Antony Chazapis
    elif request.method == 'PUT':
119 b956618e Antony Chazapis
        return object_write(request, v_account, v_container, v_object)
120 b956618e Antony Chazapis
    elif request.method == 'COPY':
121 b956618e Antony Chazapis
        return object_copy(request, v_account, v_container, v_object)
122 b956618e Antony Chazapis
    elif request.method == 'MOVE':
123 b956618e Antony Chazapis
        return object_move(request, v_account, v_container, v_object)
124 b956618e Antony Chazapis
    elif request.method == 'POST':
125 1d5c57d3 Antony Chazapis
        if request.META.get('CONTENT_TYPE', '').startswith('multipart/form-data'):
126 1d5c57d3 Antony Chazapis
            return object_write_form(request, v_account, v_container, v_object)
127 b956618e Antony Chazapis
        return object_update(request, v_account, v_container, v_object)
128 b956618e Antony Chazapis
    elif request.method == 'DELETE':
129 b956618e Antony Chazapis
        return object_delete(request, v_account, v_container, v_object)
130 b956618e Antony Chazapis
    else:
131 b956618e Antony Chazapis
        return method_not_allowed(request)
132 b956618e Antony Chazapis
133 a70c5269 Antony Chazapis
@api_method('GET', user_required=False)
134 b956618e Antony Chazapis
def authenticate(request):
135 b956618e Antony Chazapis
    # Normal Response Codes: 204
136 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
137 297513ba Antony Chazapis
    #                       forbidden (403),
138 b956618e Antony Chazapis
    #                       badRequest (400)
139 b956618e Antony Chazapis
    
140 b956618e Antony Chazapis
    x_auth_user = request.META.get('HTTP_X_AUTH_USER')
141 b956618e Antony Chazapis
    x_auth_key = request.META.get('HTTP_X_AUTH_KEY')
142 b956618e Antony Chazapis
    if not x_auth_user or not x_auth_key:
143 b956618e Antony Chazapis
        raise BadRequest('Missing X-Auth-User or X-Auth-Key header')
144 b956618e Antony Chazapis
    response = HttpResponse(status=204)
145 f7667baf Antony Chazapis
    
146 f7667baf Antony Chazapis
    uri = request.build_absolute_uri()
147 f7667baf Antony Chazapis
    if '?' in uri:
148 f7667baf Antony Chazapis
        uri = uri[:uri.find('?')]
149 f7667baf Antony Chazapis
    
150 36d368f7 Antony Chazapis
    response['X-Auth-Token'] = x_auth_key
151 af6d3b5d Antony Chazapis
    response['X-Storage-Url'] = uri + ('' if uri.endswith('/') else '/') + x_auth_user
152 b956618e Antony Chazapis
    return response
153 b956618e Antony Chazapis
154 f6c97079 Antony Chazapis
@api_method('GET', format_allowed=True)
155 f6c97079 Antony Chazapis
def account_list(request):
156 f6c97079 Antony Chazapis
    # Normal Response Codes: 200, 204
157 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
158 f6c97079 Antony Chazapis
    #                       badRequest (400)
159 f6c97079 Antony Chazapis
    
160 f6c97079 Antony Chazapis
    response = HttpResponse()
161 f6c97079 Antony Chazapis
    
162 f6c97079 Antony Chazapis
    marker = request.GET.get('marker')
163 f6c97079 Antony Chazapis
    limit = get_int_parameter(request.GET.get('limit'))
164 f6c97079 Antony Chazapis
    if not limit:
165 f6c97079 Antony Chazapis
        limit = 10000
166 f6c97079 Antony Chazapis
    
167 61efb530 Antony Chazapis
    accounts = request.backend.list_accounts(request.user_uniq, marker, limit)
168 f6c97079 Antony Chazapis
    
169 f6c97079 Antony Chazapis
    if request.serialization == 'text':
170 f6c97079 Antony Chazapis
        if len(accounts) == 0:
171 f6c97079 Antony Chazapis
            # The cloudfiles python bindings expect 200 if json/xml.
172 f6c97079 Antony Chazapis
            response.status_code = 204
173 f6c97079 Antony Chazapis
            return response
174 f6c97079 Antony Chazapis
        response.status_code = 200
175 f6c97079 Antony Chazapis
        response.content = '\n'.join(accounts) + '\n'
176 f6c97079 Antony Chazapis
        return response
177 f6c97079 Antony Chazapis
    
178 f6c97079 Antony Chazapis
    account_meta = []
179 f6c97079 Antony Chazapis
    for x in accounts:
180 690747fe Antony Chazapis
        if x == request.user_uniq:
181 690747fe Antony Chazapis
            continue
182 f6c97079 Antony Chazapis
        try:
183 82482e2c Antony Chazapis
            meta = request.backend.get_account_meta(request.user_uniq, x, 'pithos', include_user_defined=False)
184 61efb530 Antony Chazapis
            groups = request.backend.get_account_groups(request.user_uniq, x)
185 f6c97079 Antony Chazapis
        except NotAllowedError:
186 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
187 f6c97079 Antony Chazapis
        else:
188 804e8fe7 Antony Chazapis
            rename_meta_key(meta, 'modified', 'last_modified')
189 804e8fe7 Antony Chazapis
            rename_meta_key(meta, 'until_timestamp', 'x_account_until_timestamp')
190 690747fe Antony Chazapis
            if groups:
191 690747fe Antony Chazapis
                meta['X-Account-Group'] = printable_header_dict(dict([(k, ','.join(v)) for k, v in groups.iteritems()]))
192 f6c97079 Antony Chazapis
            account_meta.append(printable_header_dict(meta))
193 f6c97079 Antony Chazapis
    if request.serialization == 'xml':
194 f6c97079 Antony Chazapis
        data = render_to_string('accounts.xml', {'accounts': account_meta})
195 f6c97079 Antony Chazapis
    elif request.serialization  == 'json':
196 f6c97079 Antony Chazapis
        data = json.dumps(account_meta)
197 f6c97079 Antony Chazapis
    response.status_code = 200
198 f6c97079 Antony Chazapis
    response.content = data
199 f6c97079 Antony Chazapis
    return response
200 f6c97079 Antony Chazapis
201 b956618e Antony Chazapis
@api_method('HEAD')
202 b956618e Antony Chazapis
def account_meta(request, v_account):
203 b956618e Antony Chazapis
    # Normal Response Codes: 204
204 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
205 297513ba Antony Chazapis
    #                       forbidden (403),
206 b956618e Antony Chazapis
    #                       badRequest (400)
207 b956618e Antony Chazapis
    
208 1495b972 Antony Chazapis
    until = get_int_parameter(request.GET.get('until'))
209 cca6c617 Antony Chazapis
    try:
210 808cea65 Antony Chazapis
        meta = request.backend.get_account_meta(request.user_uniq, v_account, 'pithos', until)
211 61efb530 Antony Chazapis
        groups = request.backend.get_account_groups(request.user_uniq, v_account)
212 61efb530 Antony Chazapis
        policy = request.backend.get_account_policy(request.user_uniq, v_account)
213 cca6c617 Antony Chazapis
    except NotAllowedError:
214 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
215 b956618e Antony Chazapis
    
216 a8326bef Antony Chazapis
    validate_modification_preconditions(request, meta)
217 a8326bef Antony Chazapis
    
218 b956618e Antony Chazapis
    response = HttpResponse(status=204)
219 647a5f48 Antony Chazapis
    put_account_headers(response, meta, groups, policy)
220 b956618e Antony Chazapis
    return response
221 b956618e Antony Chazapis
222 b956618e Antony Chazapis
@api_method('POST')
223 b956618e Antony Chazapis
def account_update(request, v_account):
224 b956618e Antony Chazapis
    # Normal Response Codes: 202
225 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
226 297513ba Antony Chazapis
    #                       forbidden (403),
227 b956618e Antony Chazapis
    #                       badRequest (400)
228 b956618e Antony Chazapis
    
229 02c0c3fa Antony Chazapis
    meta, groups = get_account_headers(request)
230 a6eb13e9 Antony Chazapis
    replace = True
231 a6eb13e9 Antony Chazapis
    if 'update' in request.GET:
232 b18ef3ad Antony Chazapis
        replace = False
233 02c0c3fa Antony Chazapis
    if groups:
234 02c0c3fa Antony Chazapis
        try:
235 61efb530 Antony Chazapis
            request.backend.update_account_groups(request.user_uniq, v_account,
236 39593b2b Giorgos Verigakis
                                                    groups, replace)
237 02c0c3fa Antony Chazapis
        except NotAllowedError:
238 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
239 02c0c3fa Antony Chazapis
        except ValueError:
240 02c0c3fa Antony Chazapis
            raise BadRequest('Invalid groups header')
241 77edd23d Antony Chazapis
    if meta or replace:
242 77edd23d Antony Chazapis
        try:
243 808cea65 Antony Chazapis
            request.backend.update_account_meta(request.user_uniq, v_account,
244 808cea65 Antony Chazapis
                                                'pithos', meta, replace)
245 77edd23d Antony Chazapis
        except NotAllowedError:
246 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
247 b956618e Antony Chazapis
    return HttpResponse(status=202)
248 b956618e Antony Chazapis
249 b956618e Antony Chazapis
@api_method('GET', format_allowed=True)
250 b956618e Antony Chazapis
def container_list(request, v_account):
251 b956618e Antony Chazapis
    # Normal Response Codes: 200, 204
252 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
253 b956618e Antony Chazapis
    #                       itemNotFound (404),
254 297513ba Antony Chazapis
    #                       forbidden (403),
255 b956618e Antony Chazapis
    #                       badRequest (400)
256 b956618e Antony Chazapis
    
257 1495b972 Antony Chazapis
    until = get_int_parameter(request.GET.get('until'))
258 cca6c617 Antony Chazapis
    try:
259 808cea65 Antony Chazapis
        meta = request.backend.get_account_meta(request.user_uniq, v_account, 'pithos', until)
260 61efb530 Antony Chazapis
        groups = request.backend.get_account_groups(request.user_uniq, v_account)
261 61efb530 Antony Chazapis
        policy = request.backend.get_account_policy(request.user_uniq, v_account)
262 cca6c617 Antony Chazapis
    except NotAllowedError:
263 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
264 b956618e Antony Chazapis
    
265 b956618e Antony Chazapis
    validate_modification_preconditions(request, meta)
266 b956618e Antony Chazapis
    
267 b956618e Antony Chazapis
    response = HttpResponse()
268 647a5f48 Antony Chazapis
    put_account_headers(response, meta, groups, policy)
269 b956618e Antony Chazapis
    
270 b956618e Antony Chazapis
    marker = request.GET.get('marker')
271 f6c97079 Antony Chazapis
    limit = get_int_parameter(request.GET.get('limit'))
272 f6c97079 Antony Chazapis
    if not limit:
273 f6c97079 Antony Chazapis
        limit = 10000
274 b956618e Antony Chazapis
    
275 b18ef3ad Antony Chazapis
    shared = False
276 b18ef3ad Antony Chazapis
    if 'shared' in request.GET:
277 b18ef3ad Antony Chazapis
        shared = True
278 b18ef3ad Antony Chazapis
    
279 b956618e Antony Chazapis
    try:
280 61efb530 Antony Chazapis
        containers = request.backend.list_containers(request.user_uniq, v_account,
281 39593b2b Giorgos Verigakis
                                                marker, limit, shared, until)
282 cca6c617 Antony Chazapis
    except NotAllowedError:
283 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
284 b956618e Antony Chazapis
    except NameError:
285 b956618e Antony Chazapis
        containers = []
286 b956618e Antony Chazapis
    
287 b956618e Antony Chazapis
    if request.serialization == 'text':
288 b956618e Antony Chazapis
        if len(containers) == 0:
289 b956618e Antony Chazapis
            # The cloudfiles python bindings expect 200 if json/xml.
290 b956618e Antony Chazapis
            response.status_code = 204
291 b956618e Antony Chazapis
            return response
292 b956618e Antony Chazapis
        response.status_code = 200
293 f6c97079 Antony Chazapis
        response.content = '\n'.join(containers) + '\n'
294 b956618e Antony Chazapis
        return response
295 b956618e Antony Chazapis
    
296 b956618e Antony Chazapis
    container_meta = []
297 b956618e Antony Chazapis
    for x in containers:
298 f6c97079 Antony Chazapis
        try:
299 61efb530 Antony Chazapis
            meta = request.backend.get_container_meta(request.user_uniq, v_account,
300 82482e2c Antony Chazapis
                                                        x, 'pithos', until, include_user_defined=False)
301 61efb530 Antony Chazapis
            policy = request.backend.get_container_policy(request.user_uniq,
302 39593b2b Giorgos Verigakis
                                                            v_account, x)
303 f6c97079 Antony Chazapis
        except NotAllowedError:
304 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
305 f6c97079 Antony Chazapis
        except NameError:
306 f6c97079 Antony Chazapis
            pass
307 f6c97079 Antony Chazapis
        else:
308 804e8fe7 Antony Chazapis
            rename_meta_key(meta, 'modified', 'last_modified')
309 804e8fe7 Antony Chazapis
            rename_meta_key(meta, 'until_timestamp', 'x_container_until_timestamp')
310 690747fe Antony Chazapis
            if policy:
311 690747fe Antony Chazapis
                meta['X-Container-Policy'] = printable_header_dict(dict([(k, v) for k, v in policy.iteritems()]))
312 f6c97079 Antony Chazapis
            container_meta.append(printable_header_dict(meta))
313 b956618e Antony Chazapis
    if request.serialization == 'xml':
314 2c22e4ac Antony Chazapis
        data = render_to_string('containers.xml', {'account': v_account, 'containers': container_meta})
315 b956618e Antony Chazapis
    elif request.serialization  == 'json':
316 b956618e Antony Chazapis
        data = json.dumps(container_meta)
317 b956618e Antony Chazapis
    response.status_code = 200
318 b956618e Antony Chazapis
    response.content = data
319 b956618e Antony Chazapis
    return response
320 b956618e Antony Chazapis
321 b956618e Antony Chazapis
@api_method('HEAD')
322 b956618e Antony Chazapis
def container_meta(request, v_account, v_container):
323 b956618e Antony Chazapis
    # Normal Response Codes: 204
324 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
325 b956618e Antony Chazapis
    #                       itemNotFound (404),
326 297513ba Antony Chazapis
    #                       forbidden (403),
327 b956618e Antony Chazapis
    #                       badRequest (400)
328 b956618e Antony Chazapis
    
329 1495b972 Antony Chazapis
    until = get_int_parameter(request.GET.get('until'))
330 b956618e Antony Chazapis
    try:
331 61efb530 Antony Chazapis
        meta = request.backend.get_container_meta(request.user_uniq, v_account,
332 808cea65 Antony Chazapis
                                                    v_container, 'pithos', until)
333 371d907a Antony Chazapis
        meta['object_meta'] = request.backend.list_container_meta(request.user_uniq,
334 808cea65 Antony Chazapis
                                                v_account, v_container, 'pithos', until)
335 61efb530 Antony Chazapis
        policy = request.backend.get_container_policy(request.user_uniq, v_account,
336 39593b2b Giorgos Verigakis
                                                        v_container)
337 cca6c617 Antony Chazapis
    except NotAllowedError:
338 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
339 b956618e Antony Chazapis
    except NameError:
340 b956618e Antony Chazapis
        raise ItemNotFound('Container does not exist')
341 b956618e Antony Chazapis
    
342 a8326bef Antony Chazapis
    validate_modification_preconditions(request, meta)
343 a8326bef Antony Chazapis
    
344 b956618e Antony Chazapis
    response = HttpResponse(status=204)
345 39593b2b Giorgos Verigakis
    put_container_headers(request, response, meta, policy)
346 b956618e Antony Chazapis
    return response
347 b956618e Antony Chazapis
348 b956618e Antony Chazapis
@api_method('PUT')
349 b956618e Antony Chazapis
def container_create(request, v_account, v_container):
350 b956618e Antony Chazapis
    # Normal Response Codes: 201, 202
351 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
352 b956618e Antony Chazapis
    #                       itemNotFound (404),
353 297513ba Antony Chazapis
    #                       forbidden (403),
354 b956618e Antony Chazapis
    #                       badRequest (400)
355 b956618e Antony Chazapis
    
356 3ab38c43 Antony Chazapis
    meta, policy = get_container_headers(request)
357 b956618e Antony Chazapis
    
358 b956618e Antony Chazapis
    try:
359 61efb530 Antony Chazapis
        request.backend.put_container(request.user_uniq, v_account, v_container, policy)
360 b956618e Antony Chazapis
        ret = 201
361 cca6c617 Antony Chazapis
    except NotAllowedError:
362 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
363 a8326bef Antony Chazapis
    except ValueError:
364 a8326bef Antony Chazapis
        raise BadRequest('Invalid policy header')
365 b956618e Antony Chazapis
    except NameError:
366 b956618e Antony Chazapis
        ret = 202
367 b956618e Antony Chazapis
    
368 c5d308c6 Antony Chazapis
    if ret == 202 and policy:
369 cca6c617 Antony Chazapis
        try:
370 61efb530 Antony Chazapis
            request.backend.update_container_policy(request.user_uniq, v_account,
371 39593b2b Giorgos Verigakis
                                            v_container, policy, replace=False)
372 c5d308c6 Antony Chazapis
        except NotAllowedError:
373 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
374 c5d308c6 Antony Chazapis
        except NameError:
375 c5d308c6 Antony Chazapis
            raise ItemNotFound('Container does not exist')
376 c5d308c6 Antony Chazapis
        except ValueError:
377 c5d308c6 Antony Chazapis
            raise BadRequest('Invalid policy header')
378 c5d308c6 Antony Chazapis
    if meta:
379 c5d308c6 Antony Chazapis
        try:
380 61efb530 Antony Chazapis
            request.backend.update_container_meta(request.user_uniq, v_account,
381 808cea65 Antony Chazapis
                                            v_container, 'pithos', meta, replace=False)
382 cca6c617 Antony Chazapis
        except NotAllowedError:
383 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
384 cca6c617 Antony Chazapis
        except NameError:
385 cca6c617 Antony Chazapis
            raise ItemNotFound('Container does not exist')
386 b956618e Antony Chazapis
    
387 b956618e Antony Chazapis
    return HttpResponse(status=ret)
388 b956618e Antony Chazapis
389 af7bb62f Antony Chazapis
@api_method('POST', format_allowed=True)
390 b956618e Antony Chazapis
def container_update(request, v_account, v_container):
391 b956618e Antony Chazapis
    # Normal Response Codes: 202
392 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
393 b956618e Antony Chazapis
    #                       itemNotFound (404),
394 297513ba Antony Chazapis
    #                       forbidden (403),
395 b956618e Antony Chazapis
    #                       badRequest (400)
396 b956618e Antony Chazapis
    
397 3ab38c43 Antony Chazapis
    meta, policy = get_container_headers(request)
398 a6eb13e9 Antony Chazapis
    replace = True
399 a6eb13e9 Antony Chazapis
    if 'update' in request.GET:
400 a6eb13e9 Antony Chazapis
        replace = False
401 3ab38c43 Antony Chazapis
    if policy:
402 3ab38c43 Antony Chazapis
        try:
403 61efb530 Antony Chazapis
            request.backend.update_container_policy(request.user_uniq, v_account,
404 39593b2b Giorgos Verigakis
                                                v_container, policy, replace)
405 3ab38c43 Antony Chazapis
        except NotAllowedError:
406 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
407 3ab38c43 Antony Chazapis
        except NameError:
408 3ab38c43 Antony Chazapis
            raise ItemNotFound('Container does not exist')
409 3ab38c43 Antony Chazapis
        except ValueError:
410 3ab38c43 Antony Chazapis
            raise BadRequest('Invalid policy header')
411 77edd23d Antony Chazapis
    if meta or replace:
412 77edd23d Antony Chazapis
        try:
413 61efb530 Antony Chazapis
            request.backend.update_container_meta(request.user_uniq, v_account,
414 808cea65 Antony Chazapis
                                                    v_container, 'pithos', meta, replace)
415 77edd23d Antony Chazapis
        except NotAllowedError:
416 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
417 77edd23d Antony Chazapis
        except NameError:
418 77edd23d Antony Chazapis
            raise ItemNotFound('Container does not exist')
419 5144b772 Antony Chazapis
    
420 5144b772 Antony Chazapis
    content_length = -1
421 5144b772 Antony Chazapis
    if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
422 5144b772 Antony Chazapis
        content_length = get_int_parameter(request.META.get('CONTENT_LENGTH', 0))
423 bd08f5c0 Antony Chazapis
    content_type = request.META.get('CONTENT_TYPE')
424 5144b772 Antony Chazapis
    hashmap = []
425 5144b772 Antony Chazapis
    if content_type and content_type == 'application/octet-stream' and content_length != 0:
426 5144b772 Antony Chazapis
        for data in socket_read_iterator(request, content_length,
427 5144b772 Antony Chazapis
                                            request.backend.block_size):
428 5144b772 Antony Chazapis
            # TODO: Raise 408 (Request Timeout) if this takes too long.
429 5144b772 Antony Chazapis
            # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
430 5144b772 Antony Chazapis
            hashmap.append(request.backend.put_block(data))
431 5144b772 Antony Chazapis
    
432 5144b772 Antony Chazapis
    response = HttpResponse(status=202)
433 5144b772 Antony Chazapis
    if hashmap:
434 af7bb62f Antony Chazapis
        response.content = simple_list_response(request, hashmap)
435 5144b772 Antony Chazapis
    return response
436 b956618e Antony Chazapis
437 b956618e Antony Chazapis
@api_method('DELETE')
438 b956618e Antony Chazapis
def container_delete(request, v_account, v_container):
439 b956618e Antony Chazapis
    # Normal Response Codes: 204
440 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
441 b956618e Antony Chazapis
    #                       conflict (409),
442 b956618e Antony Chazapis
    #                       itemNotFound (404),
443 297513ba Antony Chazapis
    #                       forbidden (403),
444 b956618e Antony Chazapis
    #                       badRequest (400)
445 b956618e Antony Chazapis
    
446 bbd20b55 Antony Chazapis
    until = get_int_parameter(request.GET.get('until'))
447 b956618e Antony Chazapis
    try:
448 61efb530 Antony Chazapis
        request.backend.delete_container(request.user_uniq, v_account, v_container,
449 39593b2b Giorgos Verigakis
                                            until)
450 cca6c617 Antony Chazapis
    except NotAllowedError:
451 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
452 b956618e Antony Chazapis
    except NameError:
453 b956618e Antony Chazapis
        raise ItemNotFound('Container does not exist')
454 b956618e Antony Chazapis
    except IndexError:
455 b956618e Antony Chazapis
        raise Conflict('Container is not empty')
456 b956618e Antony Chazapis
    return HttpResponse(status=204)
457 b956618e Antony Chazapis
458 b956618e Antony Chazapis
@api_method('GET', format_allowed=True)
459 b956618e Antony Chazapis
def object_list(request, v_account, v_container):
460 b956618e Antony Chazapis
    # Normal Response Codes: 200, 204
461 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
462 b956618e Antony Chazapis
    #                       itemNotFound (404),
463 297513ba Antony Chazapis
    #                       forbidden (403),
464 b956618e Antony Chazapis
    #                       badRequest (400)
465 b956618e Antony Chazapis
    
466 1495b972 Antony Chazapis
    until = get_int_parameter(request.GET.get('until'))
467 b956618e Antony Chazapis
    try:
468 61efb530 Antony Chazapis
        meta = request.backend.get_container_meta(request.user_uniq, v_account,
469 808cea65 Antony Chazapis
                                                    v_container, 'pithos', until)
470 371d907a Antony Chazapis
        meta['object_meta'] = request.backend.list_container_meta(request.user_uniq,
471 808cea65 Antony Chazapis
                                                v_account, v_container, 'pithos', until)
472 61efb530 Antony Chazapis
        policy = request.backend.get_container_policy(request.user_uniq, v_account,
473 39593b2b Giorgos Verigakis
                                                        v_container)
474 cca6c617 Antony Chazapis
    except NotAllowedError:
475 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
476 b956618e Antony Chazapis
    except NameError:
477 b956618e Antony Chazapis
        raise ItemNotFound('Container does not exist')
478 b956618e Antony Chazapis
    
479 b956618e Antony Chazapis
    validate_modification_preconditions(request, meta)
480 b956618e Antony Chazapis
    
481 b956618e Antony Chazapis
    response = HttpResponse()
482 39593b2b Giorgos Verigakis
    put_container_headers(request, response, meta, policy)
483 b956618e Antony Chazapis
    
484 b956618e Antony Chazapis
    path = request.GET.get('path')
485 b956618e Antony Chazapis
    prefix = request.GET.get('prefix')
486 b956618e Antony Chazapis
    delimiter = request.GET.get('delimiter')
487 b956618e Antony Chazapis
    
488 b956618e Antony Chazapis
    # Path overrides prefix and delimiter.
489 b956618e Antony Chazapis
    virtual = True
490 b956618e Antony Chazapis
    if path:
491 b956618e Antony Chazapis
        prefix = path
492 b956618e Antony Chazapis
        delimiter = '/'
493 b956618e Antony Chazapis
        virtual = False
494 b956618e Antony Chazapis
    
495 b956618e Antony Chazapis
    # Naming policy.
496 b956618e Antony Chazapis
    if prefix and delimiter:
497 b956618e Antony Chazapis
        prefix = prefix + delimiter
498 b956618e Antony Chazapis
    if not prefix:
499 b956618e Antony Chazapis
        prefix = ''
500 b956618e Antony Chazapis
    prefix = prefix.lstrip('/')
501 b956618e Antony Chazapis
    
502 b956618e Antony Chazapis
    marker = request.GET.get('marker')
503 f6c97079 Antony Chazapis
    limit = get_int_parameter(request.GET.get('limit'))
504 f6c97079 Antony Chazapis
    if not limit:
505 f6c97079 Antony Chazapis
        limit = 10000
506 b956618e Antony Chazapis
    
507 22dab079 Antony Chazapis
    keys = request.GET.get('meta')
508 22dab079 Antony Chazapis
    if keys:
509 1713c946 Antony Chazapis
        keys = [smart_str(x.strip()) for x in keys.split(',') if x.strip() != '']
510 1713c946 Antony Chazapis
        included, excluded, opers = parse_filters(keys)
511 1713c946 Antony Chazapis
        keys = []
512 1713c946 Antony Chazapis
        keys += [format_header_key('X-Object-Meta-' + x) for x in included]
513 3d13f97a Sofia Papagiannaki
        keys += ['!'+format_header_key('X-Object-Meta-' + x) for x in excluded]
514 1713c946 Antony Chazapis
        keys += ['%s%s%s' % (format_header_key('X-Object-Meta-' + k), o, v) for k, o, v in opers]
515 22dab079 Antony Chazapis
    else:
516 22dab079 Antony Chazapis
        keys = []
517 22dab079 Antony Chazapis
    
518 b18ef3ad Antony Chazapis
    shared = False
519 b18ef3ad Antony Chazapis
    if 'shared' in request.GET:
520 b18ef3ad Antony Chazapis
        shared = True
521 b18ef3ad Antony Chazapis
    
522 b956618e Antony Chazapis
    if request.serialization == 'text':
523 371d907a Antony Chazapis
        try:
524 371d907a Antony Chazapis
            objects = request.backend.list_objects(request.user_uniq, v_account,
525 371d907a Antony Chazapis
                                        v_container, prefix, delimiter, marker,
526 371d907a Antony Chazapis
                                        limit, virtual, 'pithos', keys, shared, until)
527 371d907a Antony Chazapis
        except NotAllowedError:
528 371d907a Antony Chazapis
            raise Forbidden('Not allowed')
529 371d907a Antony Chazapis
        except NameError:
530 371d907a Antony Chazapis
            raise ItemNotFound('Container does not exist')
531 371d907a Antony Chazapis
        
532 b956618e Antony Chazapis
        if len(objects) == 0:
533 b956618e Antony Chazapis
            # The cloudfiles python bindings expect 200 if json/xml.
534 b956618e Antony Chazapis
            response.status_code = 204
535 b956618e Antony Chazapis
            return response
536 b956618e Antony Chazapis
        response.status_code = 200
537 83dd59c5 Antony Chazapis
        response.content = '\n'.join([x[0] for x in objects]) + '\n'
538 b956618e Antony Chazapis
        return response
539 b956618e Antony Chazapis
    
540 371d907a Antony Chazapis
    try:
541 371d907a Antony Chazapis
        objects = request.backend.list_object_meta(request.user_uniq, v_account,
542 371d907a Antony Chazapis
                                    v_container, prefix, delimiter, marker,
543 371d907a Antony Chazapis
                                    limit, virtual, 'pithos', keys, shared, until)
544 15a96c3e Antony Chazapis
        object_permissions = {}
545 15a96c3e Antony Chazapis
        object_public = {}
546 15a96c3e Antony Chazapis
        if until is None:
547 15a96c3e Antony Chazapis
            name_idx = len('/'.join((v_account, v_container, '')))
548 15a96c3e Antony Chazapis
            for x in request.backend.list_object_permissions(request.user_uniq,
549 15a96c3e Antony Chazapis
                                    v_account, v_container, prefix):
550 15a96c3e Antony Chazapis
                object = x[name_idx:]
551 15a96c3e Antony Chazapis
                object_permissions[object] = request.backend.get_object_permissions(
552 15a96c3e Antony Chazapis
                                    request.user_uniq, v_account, v_container, object)
553 15a96c3e Antony Chazapis
            for k, v in request.backend.list_object_public(request.user_uniq,
554 15a96c3e Antony Chazapis
                                    v_account, v_container, prefix).iteritems():
555 15a96c3e Antony Chazapis
                object_public[k[name_idx:]] = v
556 371d907a Antony Chazapis
    except NotAllowedError:
557 371d907a Antony Chazapis
        raise Forbidden('Not allowed')
558 371d907a Antony Chazapis
    except NameError:
559 371d907a Antony Chazapis
        raise ItemNotFound('Container does not exist')
560 371d907a Antony Chazapis
    
561 b956618e Antony Chazapis
    object_meta = []
562 371d907a Antony Chazapis
    for meta in objects:
563 371d907a Antony Chazapis
        if len(meta) == 1:
564 b956618e Antony Chazapis
            # Virtual objects/directories.
565 371d907a Antony Chazapis
            object_meta.append(meta)
566 83dd59c5 Antony Chazapis
        else:
567 371d907a Antony Chazapis
            rename_meta_key(meta, 'hash', 'x_object_hash') # Will be replaced by checksum.
568 371d907a Antony Chazapis
            rename_meta_key(meta, 'checksum', 'hash')
569 371d907a Antony Chazapis
            rename_meta_key(meta, 'type', 'content_type')
570 371d907a Antony Chazapis
            rename_meta_key(meta, 'uuid', 'x_object_uuid')
571 371d907a Antony Chazapis
            if until is not None and 'modified' in meta:
572 371d907a Antony Chazapis
                del(meta['modified'])
573 038f1ae9 Antony Chazapis
            else:
574 804e8fe7 Antony Chazapis
                rename_meta_key(meta, 'modified', 'last_modified')
575 371d907a Antony Chazapis
            rename_meta_key(meta, 'modified_by', 'x_object_modified_by')
576 371d907a Antony Chazapis
            rename_meta_key(meta, 'version', 'x_object_version')
577 371d907a Antony Chazapis
            rename_meta_key(meta, 'version_timestamp', 'x_object_version_timestamp')
578 15a96c3e Antony Chazapis
            permissions = object_permissions.get(meta['name'], None)
579 15a96c3e Antony Chazapis
            if permissions:
580 15a96c3e Antony Chazapis
                update_sharing_meta(request, permissions, v_account, v_container, meta['name'], meta)
581 15a96c3e Antony Chazapis
            public = object_public.get(meta['name'], None)
582 15a96c3e Antony Chazapis
            if public:
583 15a96c3e Antony Chazapis
                update_public_meta(public, meta)
584 371d907a Antony Chazapis
            object_meta.append(printable_header_dict(meta))
585 b956618e Antony Chazapis
    if request.serialization == 'xml':
586 b956618e Antony Chazapis
        data = render_to_string('objects.xml', {'container': v_container, 'objects': object_meta})
587 b956618e Antony Chazapis
    elif request.serialization  == 'json':
588 c48acbfd Sofia Papagiannaki
        data = json.dumps(object_meta, default=json_encode_decimal)
589 b956618e Antony Chazapis
    response.status_code = 200
590 b956618e Antony Chazapis
    response.content = data
591 b956618e Antony Chazapis
    return response
592 b956618e Antony Chazapis
593 b956618e Antony Chazapis
@api_method('HEAD')
594 b956618e Antony Chazapis
def object_meta(request, v_account, v_container, v_object):
595 b956618e Antony Chazapis
    # Normal Response Codes: 204
596 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
597 b956618e Antony Chazapis
    #                       itemNotFound (404),
598 297513ba Antony Chazapis
    #                       forbidden (403),
599 b956618e Antony Chazapis
    #                       badRequest (400)
600 b956618e Antony Chazapis
    
601 104626e3 Antony Chazapis
    version = request.GET.get('version')
602 b956618e Antony Chazapis
    try:
603 61efb530 Antony Chazapis
        meta = request.backend.get_object_meta(request.user_uniq, v_account,
604 808cea65 Antony Chazapis
                                                v_container, v_object, 'pithos', version)
605 e8886082 Antony Chazapis
        if version is None:
606 61efb530 Antony Chazapis
            permissions = request.backend.get_object_permissions(request.user_uniq,
607 39593b2b Giorgos Verigakis
                                            v_account, v_container, v_object)
608 61efb530 Antony Chazapis
            public = request.backend.get_object_public(request.user_uniq, v_account,
609 39593b2b Giorgos Verigakis
                                                        v_container, v_object)
610 e8886082 Antony Chazapis
        else:
611 e8886082 Antony Chazapis
            permissions = None
612 e0f916bb Antony Chazapis
            public = None
613 cca6c617 Antony Chazapis
    except NotAllowedError:
614 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
615 b956618e Antony Chazapis
    except NameError:
616 b956618e Antony Chazapis
        raise ItemNotFound('Object does not exist')
617 58a6c894 Antony Chazapis
    except IndexError:
618 58a6c894 Antony Chazapis
        raise ItemNotFound('Version does not exist')
619 b956618e Antony Chazapis
    
620 8cb45c13 Antony Chazapis
    update_manifest_meta(request, v_account, meta)
621 067cf1fc Antony Chazapis
    update_sharing_meta(request, permissions, v_account, v_container, v_object, meta)
622 e0f916bb Antony Chazapis
    update_public_meta(public, meta)
623 8cb45c13 Antony Chazapis
    
624 a8326bef Antony Chazapis
    # Evaluate conditions.
625 a8326bef Antony Chazapis
    validate_modification_preconditions(request, meta)
626 a8326bef Antony Chazapis
    try:
627 a8326bef Antony Chazapis
        validate_matching_preconditions(request, meta)
628 a8326bef Antony Chazapis
    except NotModified:
629 a8326bef Antony Chazapis
        response = HttpResponse(status=304)
630 33b4e4a6 Antony Chazapis
        response['ETag'] = meta['checksum']
631 a8326bef Antony Chazapis
        return response
632 a8326bef Antony Chazapis
    
633 cb146cf9 Antony Chazapis
    response = HttpResponse(status=200)
634 02c0c3fa Antony Chazapis
    put_object_headers(response, meta)
635 b956618e Antony Chazapis
    return response
636 b956618e Antony Chazapis
637 22dab079 Antony Chazapis
@api_method('GET', format_allowed=True)
638 b956618e Antony Chazapis
def object_read(request, v_account, v_container, v_object):
639 b956618e Antony Chazapis
    # Normal Response Codes: 200, 206
640 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
641 b956618e Antony Chazapis
    #                       rangeNotSatisfiable (416),
642 b956618e Antony Chazapis
    #                       preconditionFailed (412),
643 b956618e Antony Chazapis
    #                       itemNotFound (404),
644 297513ba Antony Chazapis
    #                       forbidden (403),
645 b956618e Antony Chazapis
    #                       badRequest (400),
646 b956618e Antony Chazapis
    #                       notModified (304)
647 b956618e Antony Chazapis
    
648 104626e3 Antony Chazapis
    version = request.GET.get('version')
649 e8886082 Antony Chazapis
    
650 e8886082 Antony Chazapis
    # Reply with the version list. Do this first, as the object may be deleted.
651 104626e3 Antony Chazapis
    if version == 'list':
652 e8886082 Antony Chazapis
        if request.serialization == 'text':
653 e8886082 Antony Chazapis
            raise BadRequest('No format specified for version list.')
654 e8886082 Antony Chazapis
        
655 cca6c617 Antony Chazapis
        try:
656 61efb530 Antony Chazapis
            v = request.backend.list_versions(request.user_uniq, v_account,
657 39593b2b Giorgos Verigakis
                                                v_container, v_object)
658 cca6c617 Antony Chazapis
        except NotAllowedError:
659 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
660 cca6c617 Antony Chazapis
        d = {'versions': v}
661 e8886082 Antony Chazapis
        if request.serialization == 'xml':
662 e8886082 Antony Chazapis
            d['object'] = v_object
663 e8886082 Antony Chazapis
            data = render_to_string('versions.xml', d)
664 e8886082 Antony Chazapis
        elif request.serialization  == 'json':
665 c48acbfd Sofia Papagiannaki
            data = json.dumps(d, default=json_encode_decimal)
666 e8886082 Antony Chazapis
        
667 e8886082 Antony Chazapis
        response = HttpResponse(data, status=200)
668 e8886082 Antony Chazapis
        response['Content-Length'] = len(data)
669 e8886082 Antony Chazapis
        return response
670 e8886082 Antony Chazapis
    
671 b956618e Antony Chazapis
    try:
672 61efb530 Antony Chazapis
        meta = request.backend.get_object_meta(request.user_uniq, v_account,
673 808cea65 Antony Chazapis
                                                v_container, v_object, 'pithos', version)
674 e8886082 Antony Chazapis
        if version is None:
675 61efb530 Antony Chazapis
            permissions = request.backend.get_object_permissions(request.user_uniq,
676 39593b2b Giorgos Verigakis
                                            v_account, v_container, v_object)
677 61efb530 Antony Chazapis
            public = request.backend.get_object_public(request.user_uniq, v_account,
678 39593b2b Giorgos Verigakis
                                                        v_container, v_object)
679 e8886082 Antony Chazapis
        else:
680 e8886082 Antony Chazapis
            permissions = None
681 e0f916bb Antony Chazapis
            public = None
682 cca6c617 Antony Chazapis
    except NotAllowedError:
683 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
684 b956618e Antony Chazapis
    except NameError:
685 b956618e Antony Chazapis
        raise ItemNotFound('Object does not exist')
686 58a6c894 Antony Chazapis
    except IndexError:
687 58a6c894 Antony Chazapis
        raise ItemNotFound('Version does not exist')
688 b956618e Antony Chazapis
    
689 8cb45c13 Antony Chazapis
    update_manifest_meta(request, v_account, meta)
690 067cf1fc Antony Chazapis
    update_sharing_meta(request, permissions, v_account, v_container, v_object, meta)
691 e0f916bb Antony Chazapis
    update_public_meta(public, meta)
692 8cb45c13 Antony Chazapis
    
693 22dab079 Antony Chazapis
    # Evaluate conditions.
694 b956618e Antony Chazapis
    validate_modification_preconditions(request, meta)
695 22dab079 Antony Chazapis
    try:
696 22dab079 Antony Chazapis
        validate_matching_preconditions(request, meta)
697 22dab079 Antony Chazapis
    except NotModified:
698 22dab079 Antony Chazapis
        response = HttpResponse(status=304)
699 33b4e4a6 Antony Chazapis
        response['ETag'] = meta['checksum']
700 22dab079 Antony Chazapis
        return response
701 b956618e Antony Chazapis
    
702 2fd10ff4 Antony Chazapis
    hashmap_reply = False
703 2fd10ff4 Antony Chazapis
    if 'hashmap' in request.GET and request.serialization != 'text':
704 2fd10ff4 Antony Chazapis
        hashmap_reply = True
705 2fd10ff4 Antony Chazapis
    
706 8cb45c13 Antony Chazapis
    sizes = []
707 8cb45c13 Antony Chazapis
    hashmaps = []
708 2fd10ff4 Antony Chazapis
    if 'X-Object-Manifest' in meta and not hashmap_reply:
709 8cb45c13 Antony Chazapis
        try:
710 6d817842 Antony Chazapis
            src_container, src_name = split_container_object_string('/' + meta['X-Object-Manifest'])
711 61efb530 Antony Chazapis
            objects = request.backend.list_objects(request.user_uniq, v_account,
712 39593b2b Giorgos Verigakis
                                src_container, prefix=src_name, virtual=False)
713 cca6c617 Antony Chazapis
        except NotAllowedError:
714 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
715 8cb45c13 Antony Chazapis
        except ValueError:
716 8cb45c13 Antony Chazapis
            raise BadRequest('Invalid X-Object-Manifest header')
717 8cb45c13 Antony Chazapis
        except NameError:
718 8cb45c13 Antony Chazapis
            raise ItemNotFound('Container does not exist')
719 8cb45c13 Antony Chazapis
        
720 8cb45c13 Antony Chazapis
        try:
721 8cb45c13 Antony Chazapis
            for x in objects:
722 61efb530 Antony Chazapis
                s, h = request.backend.get_object_hashmap(request.user_uniq,
723 39593b2b Giorgos Verigakis
                                        v_account, src_container, x[0], x[1])
724 8cb45c13 Antony Chazapis
                sizes.append(s)
725 8cb45c13 Antony Chazapis
                hashmaps.append(h)
726 cca6c617 Antony Chazapis
        except NotAllowedError:
727 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
728 8cb45c13 Antony Chazapis
        except NameError:
729 8cb45c13 Antony Chazapis
            raise ItemNotFound('Object does not exist')
730 8cb45c13 Antony Chazapis
        except IndexError:
731 8cb45c13 Antony Chazapis
            raise ItemNotFound('Version does not exist')
732 8cb45c13 Antony Chazapis
    else:
733 8cb45c13 Antony Chazapis
        try:
734 61efb530 Antony Chazapis
            s, h = request.backend.get_object_hashmap(request.user_uniq, v_account,
735 39593b2b Giorgos Verigakis
                                                v_container, v_object, version)
736 8cb45c13 Antony Chazapis
            sizes.append(s)
737 8cb45c13 Antony Chazapis
            hashmaps.append(h)
738 cca6c617 Antony Chazapis
        except NotAllowedError:
739 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
740 8cb45c13 Antony Chazapis
        except NameError:
741 8cb45c13 Antony Chazapis
            raise ItemNotFound('Object does not exist')
742 8cb45c13 Antony Chazapis
        except IndexError:
743 8cb45c13 Antony Chazapis
            raise ItemNotFound('Version does not exist')
744 b956618e Antony Chazapis
    
745 22dab079 Antony Chazapis
    # Reply with the hashmap.
746 2fd10ff4 Antony Chazapis
    if hashmap_reply:
747 8cb45c13 Antony Chazapis
        size = sum(sizes)
748 8cb45c13 Antony Chazapis
        hashmap = sum(hashmaps, [])
749 39593b2b Giorgos Verigakis
        d = {
750 39593b2b Giorgos Verigakis
            'block_size': request.backend.block_size,
751 39593b2b Giorgos Verigakis
            'block_hash': request.backend.hash_algorithm,
752 39593b2b Giorgos Verigakis
            'bytes': size,
753 39593b2b Giorgos Verigakis
            'hashes': hashmap}
754 22dab079 Antony Chazapis
        if request.serialization == 'xml':
755 6bc372a4 Antony Chazapis
            d['object'] = v_object
756 6bc372a4 Antony Chazapis
            data = render_to_string('hashes.xml', d)
757 22dab079 Antony Chazapis
        elif request.serialization  == 'json':
758 6bc372a4 Antony Chazapis
            data = json.dumps(d)
759 22dab079 Antony Chazapis
        
760 22dab079 Antony Chazapis
        response = HttpResponse(data, status=200)
761 02c0c3fa Antony Chazapis
        put_object_headers(response, meta)
762 22dab079 Antony Chazapis
        response['Content-Length'] = len(data)
763 22dab079 Antony Chazapis
        return response
764 22dab079 Antony Chazapis
    
765 28486f26 Antony Chazapis
    request.serialization = 'text' # Unset.
766 8cb45c13 Antony Chazapis
    return object_data_response(request, sizes, hashmaps, meta)
767 b956618e Antony Chazapis
768 76985443 Sofia Papagiannaki
@api_method('PUT', format_allowed=True)
769 b956618e Antony Chazapis
def object_write(request, v_account, v_container, v_object):
770 b956618e Antony Chazapis
    # Normal Response Codes: 201
771 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
772 b956618e Antony Chazapis
    #                       unprocessableEntity (422),
773 b956618e Antony Chazapis
    #                       lengthRequired (411),
774 3436eeb0 Antony Chazapis
    #                       conflict (409),
775 b956618e Antony Chazapis
    #                       itemNotFound (404),
776 297513ba Antony Chazapis
    #                       forbidden (403),
777 b956618e Antony Chazapis
    #                       badRequest (400)
778 7278d371 Antony Chazapis
    
779 a8326bef Antony Chazapis
    # Evaluate conditions.
780 a8326bef Antony Chazapis
    if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
781 a8326bef Antony Chazapis
        try:
782 61efb530 Antony Chazapis
            meta = request.backend.get_object_meta(request.user_uniq, v_account,
783 808cea65 Antony Chazapis
                                                        v_container, v_object, 'pithos')
784 a8326bef Antony Chazapis
        except NotAllowedError:
785 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
786 a8326bef Antony Chazapis
        except NameError:
787 a8326bef Antony Chazapis
            meta = {}
788 a8326bef Antony Chazapis
        validate_matching_preconditions(request, meta)
789 a8326bef Antony Chazapis
    
790 88283e9e Antony Chazapis
    copy_from = request.META.get('HTTP_X_COPY_FROM')
791 88283e9e Antony Chazapis
    move_from = request.META.get('HTTP_X_MOVE_FROM')
792 b956618e Antony Chazapis
    if copy_from or move_from:
793 ab2e317e Antony Chazapis
        content_length = get_content_length(request) # Required by the API.
794 b956618e Antony Chazapis
        
795 88283e9e Antony Chazapis
        src_account = request.META.get('HTTP_X_SOURCE_ACCOUNT')
796 79bb41b7 Antony Chazapis
        if not src_account:
797 61efb530 Antony Chazapis
            src_account = request.user_uniq
798 b956618e Antony Chazapis
        if move_from:
799 83dd59c5 Antony Chazapis
            try:
800 83dd59c5 Antony Chazapis
                src_container, src_name = split_container_object_string(move_from)
801 83dd59c5 Antony Chazapis
            except ValueError:
802 83dd59c5 Antony Chazapis
                raise BadRequest('Invalid X-Move-From header')
803 79bb41b7 Antony Chazapis
            version_id = copy_or_move_object(request, src_account, src_container, src_name,
804 79bb41b7 Antony Chazapis
                                                v_account, v_container, v_object, move=True)
805 b956618e Antony Chazapis
        else:
806 83dd59c5 Antony Chazapis
            try:
807 83dd59c5 Antony Chazapis
                src_container, src_name = split_container_object_string(copy_from)
808 83dd59c5 Antony Chazapis
            except ValueError:
809 83dd59c5 Antony Chazapis
                raise BadRequest('Invalid X-Copy-From header')
810 79bb41b7 Antony Chazapis
            version_id = copy_or_move_object(request, src_account, src_container, src_name,
811 79bb41b7 Antony Chazapis
                                                v_account, v_container, v_object, move=False)
812 7dd293a0 Antony Chazapis
        response = HttpResponse(status=201)
813 7dd293a0 Antony Chazapis
        response['X-Object-Version'] = version_id
814 7dd293a0 Antony Chazapis
        return response
815 b956618e Antony Chazapis
    
816 66ce2ca5 Antony Chazapis
    content_type, meta, permissions, public = get_object_headers(request)
817 b956618e Antony Chazapis
    content_length = -1
818 b956618e Antony Chazapis
    if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
819 22dab079 Antony Chazapis
        content_length = get_content_length(request)
820 b956618e Antony Chazapis
    # Should be BadRequest, but API says otherwise.
821 66ce2ca5 Antony Chazapis
    if not content_type:
822 b956618e Antony Chazapis
        raise LengthRequired('Missing Content-Type header')
823 b956618e Antony Chazapis
    
824 f9f15f92 Antony Chazapis
    if 'hashmap' in request.GET:
825 f9f15f92 Antony Chazapis
        if request.serialization not in ('json', 'xml'):
826 f9f15f92 Antony Chazapis
            raise BadRequest('Invalid hashmap format')
827 f9f15f92 Antony Chazapis
        
828 76985443 Sofia Papagiannaki
        data = ''
829 39593b2b Giorgos Verigakis
        for block in socket_read_iterator(request, content_length,
830 39593b2b Giorgos Verigakis
                                            request.backend.block_size):
831 76985443 Sofia Papagiannaki
            data = '%s%s' % (data, block)
832 32a437b1 Sofia Papagiannaki
        
833 32a437b1 Sofia Papagiannaki
        if request.serialization == 'json':
834 32a437b1 Sofia Papagiannaki
            d = json.loads(data)
835 32a437b1 Sofia Papagiannaki
            if not hasattr(d, '__getitem__'):
836 32a437b1 Sofia Papagiannaki
                raise BadRequest('Invalid data formating')
837 32a437b1 Sofia Papagiannaki
            try:
838 32a437b1 Sofia Papagiannaki
                hashmap = d['hashes']
839 f899c7b4 Antony Chazapis
                size = int(d['bytes'])
840 f899c7b4 Antony Chazapis
            except:
841 32a437b1 Sofia Papagiannaki
                raise BadRequest('Invalid data formatting')
842 32a437b1 Sofia Papagiannaki
        elif request.serialization == 'xml':
843 32a437b1 Sofia Papagiannaki
            try:
844 32a437b1 Sofia Papagiannaki
                xml = minidom.parseString(data)
845 32a437b1 Sofia Papagiannaki
                obj = xml.getElementsByTagName('object')[0]
846 f899c7b4 Antony Chazapis
                size = int(obj.attributes['bytes'].value)
847 32a437b1 Sofia Papagiannaki
                
848 32a437b1 Sofia Papagiannaki
                hashes = xml.getElementsByTagName('hash')
849 32a437b1 Sofia Papagiannaki
                hashmap = []
850 32a437b1 Sofia Papagiannaki
                for hash in hashes:
851 32a437b1 Sofia Papagiannaki
                    hashmap.append(hash.firstChild.data)
852 f899c7b4 Antony Chazapis
            except:
853 32a437b1 Sofia Papagiannaki
                raise BadRequest('Invalid data formatting')
854 33b4e4a6 Antony Chazapis
        
855 33b4e4a6 Antony Chazapis
        checksum = '' # Do not set to None (will copy previous value).
856 76985443 Sofia Papagiannaki
    else:
857 76985443 Sofia Papagiannaki
        md5 = hashlib.md5()
858 76985443 Sofia Papagiannaki
        size = 0
859 76985443 Sofia Papagiannaki
        hashmap = []
860 39593b2b Giorgos Verigakis
        for data in socket_read_iterator(request, content_length,
861 39593b2b Giorgos Verigakis
                                            request.backend.block_size):
862 76985443 Sofia Papagiannaki
            # TODO: Raise 408 (Request Timeout) if this takes too long.
863 76985443 Sofia Papagiannaki
            # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
864 76985443 Sofia Papagiannaki
            size += len(data)
865 39593b2b Giorgos Verigakis
            hashmap.append(request.backend.put_block(data))
866 76985443 Sofia Papagiannaki
            md5.update(data)
867 76985443 Sofia Papagiannaki
        
868 33b4e4a6 Antony Chazapis
        checksum = md5.hexdigest().lower()
869 76985443 Sofia Papagiannaki
        etag = request.META.get('HTTP_ETAG')
870 33b4e4a6 Antony Chazapis
        if etag and parse_etags(etag)[0].lower() != checksum:
871 76985443 Sofia Papagiannaki
            raise UnprocessableEntity('Object ETag does not match')
872 22dab079 Antony Chazapis
    
873 22dab079 Antony Chazapis
    try:
874 61efb530 Antony Chazapis
        version_id = request.backend.update_object_hashmap(request.user_uniq,
875 71dbc012 Antony Chazapis
                        v_account, v_container, v_object, size, content_type,
876 33b4e4a6 Antony Chazapis
                        hashmap, checksum, 'pithos', meta, True, permissions)
877 cca6c617 Antony Chazapis
    except NotAllowedError:
878 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
879 76985443 Sofia Papagiannaki
    except IndexError, e:
880 af7bb62f Antony Chazapis
        raise Conflict(simple_list_response(request, e.data))
881 22dab079 Antony Chazapis
    except NameError:
882 22dab079 Antony Chazapis
        raise ItemNotFound('Container does not exist')
883 3436eeb0 Antony Chazapis
    except ValueError:
884 3436eeb0 Antony Chazapis
        raise BadRequest('Invalid sharing header')
885 5df6c6d1 Antony Chazapis
    except QuotaError:
886 5df6c6d1 Antony Chazapis
        raise RequestEntityTooLarge('Quota exceeded')
887 33b4e4a6 Antony Chazapis
    if not checksum:
888 cddcf432 chazapis
        # Update the MD5 after the hashmap, as there may be missing hashes.
889 33b4e4a6 Antony Chazapis
        checksum = hashmap_md5(request, hashmap, size)
890 cddcf432 chazapis
        try:
891 33b4e4a6 Antony Chazapis
            version_id = request.backend.update_object_checksum(request.user_uniq,
892 33b4e4a6 Antony Chazapis
                            v_account, v_container, v_object, version_id, checksum)
893 cddcf432 chazapis
        except NotAllowedError:
894 cddcf432 chazapis
            raise Forbidden('Not allowed')
895 e0f916bb Antony Chazapis
    if public is not None:
896 e0f916bb Antony Chazapis
        try:
897 61efb530 Antony Chazapis
            request.backend.update_object_public(request.user_uniq, v_account,
898 39593b2b Giorgos Verigakis
                                                v_container, v_object, public)
899 e0f916bb Antony Chazapis
        except NotAllowedError:
900 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
901 e0f916bb Antony Chazapis
        except NameError:
902 e0f916bb Antony Chazapis
            raise ItemNotFound('Object does not exist')
903 b956618e Antony Chazapis
    
904 1993fea9 Antony Chazapis
    response = HttpResponse(status=201)
905 33b4e4a6 Antony Chazapis
    if checksum:
906 33b4e4a6 Antony Chazapis
        response['ETag'] = checksum
907 7dd293a0 Antony Chazapis
    response['X-Object-Version'] = version_id
908 b956618e Antony Chazapis
    return response
909 b956618e Antony Chazapis
910 1d5c57d3 Antony Chazapis
@api_method('POST')
911 1d5c57d3 Antony Chazapis
def object_write_form(request, v_account, v_container, v_object):
912 1d5c57d3 Antony Chazapis
    # Normal Response Codes: 201
913 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
914 1d5c57d3 Antony Chazapis
    #                       itemNotFound (404),
915 297513ba Antony Chazapis
    #                       forbidden (403),
916 1d5c57d3 Antony Chazapis
    #                       badRequest (400)
917 1d5c57d3 Antony Chazapis
    
918 817890f2 Antony Chazapis
    request.upload_handlers = [SaveToBackendHandler(request)]
919 1d5c57d3 Antony Chazapis
    if not request.FILES.has_key('X-Object-Data'):
920 1d5c57d3 Antony Chazapis
        raise BadRequest('Missing X-Object-Data field')
921 1d5c57d3 Antony Chazapis
    file = request.FILES['X-Object-Data']
922 1d5c57d3 Antony Chazapis
    
923 33b4e4a6 Antony Chazapis
    checksum = file.etag
924 1d5c57d3 Antony Chazapis
    try:
925 61efb530 Antony Chazapis
        version_id = request.backend.update_object_hashmap(request.user_uniq,
926 33b4e4a6 Antony Chazapis
                        v_account, v_container, v_object, file.size, file.content_type,
927 33b4e4a6 Antony Chazapis
                        file.hashmap, checksum, 'pithos', {}, True)
928 1d5c57d3 Antony Chazapis
    except NotAllowedError:
929 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
930 1d5c57d3 Antony Chazapis
    except NameError:
931 1d5c57d3 Antony Chazapis
        raise ItemNotFound('Container does not exist')
932 5df6c6d1 Antony Chazapis
    except QuotaError:
933 5df6c6d1 Antony Chazapis
        raise RequestEntityTooLarge('Quota exceeded')
934 1d5c57d3 Antony Chazapis
    
935 1d5c57d3 Antony Chazapis
    response = HttpResponse(status=201)
936 33b4e4a6 Antony Chazapis
    response['ETag'] = checksum
937 7dd293a0 Antony Chazapis
    response['X-Object-Version'] = version_id
938 33b4e4a6 Antony Chazapis
    response.content = checksum
939 1d5c57d3 Antony Chazapis
    return response
940 1d5c57d3 Antony Chazapis
941 6b6b6c1e Antony Chazapis
@api_method('COPY', format_allowed=True)
942 b956618e Antony Chazapis
def object_copy(request, v_account, v_container, v_object):
943 b956618e Antony Chazapis
    # Normal Response Codes: 201
944 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
945 b956618e Antony Chazapis
    #                       itemNotFound (404),
946 297513ba Antony Chazapis
    #                       forbidden (403),
947 b956618e Antony Chazapis
    #                       badRequest (400)
948 b956618e Antony Chazapis
    
949 88283e9e Antony Chazapis
    dest_account = request.META.get('HTTP_DESTINATION_ACCOUNT')
950 79bb41b7 Antony Chazapis
    if not dest_account:
951 61efb530 Antony Chazapis
        dest_account = request.user_uniq
952 88283e9e Antony Chazapis
    dest_path = request.META.get('HTTP_DESTINATION')
953 b956618e Antony Chazapis
    if not dest_path:
954 b956618e Antony Chazapis
        raise BadRequest('Missing Destination header')
955 83dd59c5 Antony Chazapis
    try:
956 83dd59c5 Antony Chazapis
        dest_container, dest_name = split_container_object_string(dest_path)
957 83dd59c5 Antony Chazapis
    except ValueError:
958 83dd59c5 Antony Chazapis
        raise BadRequest('Invalid Destination header')
959 a8326bef Antony Chazapis
    
960 a8326bef Antony Chazapis
    # Evaluate conditions.
961 a8326bef Antony Chazapis
    if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
962 a8326bef Antony Chazapis
        src_version = request.META.get('HTTP_X_SOURCE_VERSION')
963 a8326bef Antony Chazapis
        try:
964 61efb530 Antony Chazapis
            meta = request.backend.get_object_meta(request.user_uniq, v_account,
965 808cea65 Antony Chazapis
                                            v_container, v_object, 'pithos', src_version)
966 a8326bef Antony Chazapis
        except NotAllowedError:
967 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
968 a8326bef Antony Chazapis
        except (NameError, IndexError):
969 a8326bef Antony Chazapis
            raise ItemNotFound('Container or object does not exist')
970 a8326bef Antony Chazapis
        validate_matching_preconditions(request, meta)
971 a8326bef Antony Chazapis
    
972 79bb41b7 Antony Chazapis
    version_id = copy_or_move_object(request, v_account, v_container, v_object,
973 79bb41b7 Antony Chazapis
                                        dest_account, dest_container, dest_name, move=False)
974 7dd293a0 Antony Chazapis
    response = HttpResponse(status=201)
975 7dd293a0 Antony Chazapis
    response['X-Object-Version'] = version_id
976 7dd293a0 Antony Chazapis
    return response
977 b956618e Antony Chazapis
978 6b6b6c1e Antony Chazapis
@api_method('MOVE', format_allowed=True)
979 b956618e Antony Chazapis
def object_move(request, v_account, v_container, v_object):
980 b956618e Antony Chazapis
    # Normal Response Codes: 201
981 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
982 b956618e Antony Chazapis
    #                       itemNotFound (404),
983 297513ba Antony Chazapis
    #                       forbidden (403),
984 b956618e Antony Chazapis
    #                       badRequest (400)
985 b956618e Antony Chazapis
    
986 88283e9e Antony Chazapis
    dest_account = request.META.get('HTTP_DESTINATION_ACCOUNT')
987 79bb41b7 Antony Chazapis
    if not dest_account:
988 61efb530 Antony Chazapis
        dest_account = request.user_uniq
989 88283e9e Antony Chazapis
    dest_path = request.META.get('HTTP_DESTINATION')
990 b956618e Antony Chazapis
    if not dest_path:
991 b956618e Antony Chazapis
        raise BadRequest('Missing Destination header')
992 83dd59c5 Antony Chazapis
    try:
993 83dd59c5 Antony Chazapis
        dest_container, dest_name = split_container_object_string(dest_path)
994 83dd59c5 Antony Chazapis
    except ValueError:
995 83dd59c5 Antony Chazapis
        raise BadRequest('Invalid Destination header')
996 a8326bef Antony Chazapis
    
997 a8326bef Antony Chazapis
    # Evaluate conditions.
998 a8326bef Antony Chazapis
    if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
999 a8326bef Antony Chazapis
        try:
1000 61efb530 Antony Chazapis
            meta = request.backend.get_object_meta(request.user_uniq, v_account,
1001 808cea65 Antony Chazapis
                                                    v_container, v_object, 'pithos')
1002 a8326bef Antony Chazapis
        except NotAllowedError:
1003 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
1004 a8326bef Antony Chazapis
        except NameError:
1005 a8326bef Antony Chazapis
            raise ItemNotFound('Container or object does not exist')
1006 a8326bef Antony Chazapis
        validate_matching_preconditions(request, meta)
1007 a8326bef Antony Chazapis
    
1008 79bb41b7 Antony Chazapis
    version_id = copy_or_move_object(request, v_account, v_container, v_object,
1009 79bb41b7 Antony Chazapis
                                        dest_account, dest_container, dest_name, move=True)
1010 7dd293a0 Antony Chazapis
    response = HttpResponse(status=201)
1011 7dd293a0 Antony Chazapis
    response['X-Object-Version'] = version_id
1012 7dd293a0 Antony Chazapis
    return response
1013 b956618e Antony Chazapis
1014 6b6b6c1e Antony Chazapis
@api_method('POST', format_allowed=True)
1015 b956618e Antony Chazapis
def object_update(request, v_account, v_container, v_object):
1016 e9285524 Antony Chazapis
    # Normal Response Codes: 202, 204
1017 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
1018 3436eeb0 Antony Chazapis
    #                       conflict (409),
1019 b956618e Antony Chazapis
    #                       itemNotFound (404),
1020 297513ba Antony Chazapis
    #                       forbidden (403),
1021 b956618e Antony Chazapis
    #                       badRequest (400)
1022 5bc1116c Antony Chazapis
    
1023 66ce2ca5 Antony Chazapis
    content_type, meta, permissions, public = get_object_headers(request)
1024 22dab079 Antony Chazapis
    
1025 cfe6939d Antony Chazapis
    try:
1026 61efb530 Antony Chazapis
        prev_meta = request.backend.get_object_meta(request.user_uniq, v_account,
1027 808cea65 Antony Chazapis
                                                    v_container, v_object, 'pithos')
1028 cca6c617 Antony Chazapis
    except NotAllowedError:
1029 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
1030 cfe6939d Antony Chazapis
    except NameError:
1031 cfe6939d Antony Chazapis
        raise ItemNotFound('Object does not exist')
1032 a8326bef Antony Chazapis
    
1033 a8326bef Antony Chazapis
    # Evaluate conditions.
1034 a8326bef Antony Chazapis
    if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
1035 a8326bef Antony Chazapis
        validate_matching_preconditions(request, prev_meta)
1036 a8326bef Antony Chazapis
    
1037 ac62f6da Antony Chazapis
    replace = True
1038 ac62f6da Antony Chazapis
    if 'update' in request.GET:
1039 ac62f6da Antony Chazapis
        replace = False
1040 22dab079 Antony Chazapis
    
1041 ab2e317e Antony Chazapis
    # A Content-Type or X-Source-Object header indicates data updates.
1042 ab2e317e Antony Chazapis
    src_object = request.META.get('HTTP_X_SOURCE_OBJECT')
1043 ab2e317e Antony Chazapis
    if (not content_type or content_type != 'application/octet-stream') and not src_object:
1044 77edd23d Antony Chazapis
        response = HttpResponse(status=202)
1045 77edd23d Antony Chazapis
        
1046 cca6c617 Antony Chazapis
        # Do permissions first, as it may fail easier.
1047 cca6c617 Antony Chazapis
        if permissions is not None:
1048 ac62f6da Antony Chazapis
            try:
1049 61efb530 Antony Chazapis
                request.backend.update_object_permissions(request.user_uniq,
1050 39593b2b Giorgos Verigakis
                                v_account, v_container, v_object, permissions)
1051 cca6c617 Antony Chazapis
            except NotAllowedError:
1052 297513ba Antony Chazapis
                raise Forbidden('Not allowed')
1053 ac62f6da Antony Chazapis
            except NameError:
1054 ac62f6da Antony Chazapis
                raise ItemNotFound('Object does not exist')
1055 ac62f6da Antony Chazapis
            except ValueError:
1056 ac62f6da Antony Chazapis
                raise BadRequest('Invalid sharing header')
1057 e0f916bb Antony Chazapis
        if public is not None:
1058 e0f916bb Antony Chazapis
            try:
1059 61efb530 Antony Chazapis
                request.backend.update_object_public(request.user_uniq, v_account,
1060 39593b2b Giorgos Verigakis
                                                v_container, v_object, public)
1061 e0f916bb Antony Chazapis
            except NotAllowedError:
1062 297513ba Antony Chazapis
                raise Forbidden('Not allowed')
1063 e0f916bb Antony Chazapis
            except NameError:
1064 e0f916bb Antony Chazapis
                raise ItemNotFound('Object does not exist')
1065 77edd23d Antony Chazapis
        if meta or replace:
1066 77edd23d Antony Chazapis
            try:
1067 61efb530 Antony Chazapis
                version_id = request.backend.update_object_meta(request.user_uniq,
1068 808cea65 Antony Chazapis
                                v_account, v_container, v_object, 'pithos', meta, replace)
1069 77edd23d Antony Chazapis
            except NotAllowedError:
1070 297513ba Antony Chazapis
                raise Forbidden('Not allowed')
1071 77edd23d Antony Chazapis
            except NameError:
1072 77edd23d Antony Chazapis
                raise ItemNotFound('Object does not exist')        
1073 77edd23d Antony Chazapis
            response['X-Object-Version'] = version_id
1074 7dd293a0 Antony Chazapis
        
1075 7dd293a0 Antony Chazapis
        return response
1076 ac62f6da Antony Chazapis
    
1077 cfe6939d Antony Chazapis
    # Single range update. Range must be in Content-Range.
1078 22dab079 Antony Chazapis
    # Based on: http://code.google.com/p/gears/wiki/ContentRangePostProposal
1079 cfe6939d Antony Chazapis
    # (with the addition that '*' is allowed for the range - will append).
1080 22dab079 Antony Chazapis
    content_range = request.META.get('HTTP_CONTENT_RANGE')
1081 22dab079 Antony Chazapis
    if not content_range:
1082 ac62f6da Antony Chazapis
        raise BadRequest('Missing Content-Range header')
1083 22dab079 Antony Chazapis
    ranges = get_content_range(request)
1084 22dab079 Antony Chazapis
    if not ranges:
1085 ac62f6da Antony Chazapis
        raise RangeNotSatisfiable('Invalid Content-Range header')
1086 22dab079 Antony Chazapis
    
1087 cfe6939d Antony Chazapis
    try:
1088 61efb530 Antony Chazapis
        size, hashmap = request.backend.get_object_hashmap(request.user_uniq,
1089 39593b2b Giorgos Verigakis
                                            v_account, v_container, v_object)
1090 cca6c617 Antony Chazapis
    except NotAllowedError:
1091 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
1092 cfe6939d Antony Chazapis
    except NameError:
1093 cfe6939d Antony Chazapis
        raise ItemNotFound('Object does not exist')
1094 cfe6939d Antony Chazapis
    
1095 cfe6939d Antony Chazapis
    offset, length, total = ranges
1096 cfe6939d Antony Chazapis
    if offset is None:
1097 cfe6939d Antony Chazapis
        offset = size
1098 cb146cf9 Antony Chazapis
    elif offset > size:
1099 cb146cf9 Antony Chazapis
        raise RangeNotSatisfiable('Supplied offset is beyond object limits')
1100 ab2e317e Antony Chazapis
    if src_object:
1101 88283e9e Antony Chazapis
        src_account = request.META.get('HTTP_X_SOURCE_ACCOUNT')
1102 2cd94d81 Antony Chazapis
        if not src_account:
1103 61efb530 Antony Chazapis
            src_account = request.user_uniq
1104 ab2e317e Antony Chazapis
        src_container, src_name = split_container_object_string(src_object)
1105 ab2e317e Antony Chazapis
        src_version = request.META.get('HTTP_X_SOURCE_VERSION')
1106 ab2e317e Antony Chazapis
        try:
1107 61efb530 Antony Chazapis
            src_size, src_hashmap = request.backend.get_object_hashmap(request.user_uniq,
1108 2cd94d81 Antony Chazapis
                                        src_account, src_container, src_name, src_version)
1109 ab2e317e Antony Chazapis
        except NotAllowedError:
1110 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
1111 ab2e317e Antony Chazapis
        except NameError:
1112 ab2e317e Antony Chazapis
            raise ItemNotFound('Source object does not exist')
1113 ab2e317e Antony Chazapis
        
1114 ab2e317e Antony Chazapis
        if length is None:
1115 ab2e317e Antony Chazapis
            length = src_size
1116 ab2e317e Antony Chazapis
        elif length > src_size:
1117 ab2e317e Antony Chazapis
            raise BadRequest('Object length is smaller than range length')
1118 ab2e317e Antony Chazapis
    else:
1119 ab2e317e Antony Chazapis
        # Require either a Content-Length, or 'chunked' Transfer-Encoding.
1120 ab2e317e Antony Chazapis
        content_length = -1
1121 ab2e317e Antony Chazapis
        if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
1122 ab2e317e Antony Chazapis
            content_length = get_content_length(request)
1123 b18ef3ad Antony Chazapis
        
1124 ab2e317e Antony Chazapis
        if length is None:
1125 ab2e317e Antony Chazapis
            length = content_length
1126 ab2e317e Antony Chazapis
        else:
1127 ab2e317e Antony Chazapis
            if content_length == -1:
1128 ab2e317e Antony Chazapis
                # TODO: Get up to length bytes in chunks.
1129 ab2e317e Antony Chazapis
                length = content_length
1130 ab2e317e Antony Chazapis
            elif length != content_length:
1131 ab2e317e Antony Chazapis
                raise BadRequest('Content length does not match range length')
1132 cfe6939d Antony Chazapis
    if total is not None and (total != size or offset >= size or (length > 0 and offset + length >= size)):
1133 cfe6939d Antony Chazapis
        raise RangeNotSatisfiable('Supplied range will change provided object limits')
1134 cfe6939d Antony Chazapis
    
1135 1495b972 Antony Chazapis
    dest_bytes = request.META.get('HTTP_X_OBJECT_BYTES')
1136 1495b972 Antony Chazapis
    if dest_bytes is not None:
1137 1495b972 Antony Chazapis
        dest_bytes = get_int_parameter(dest_bytes)
1138 1495b972 Antony Chazapis
        if dest_bytes is None:
1139 1495b972 Antony Chazapis
            raise BadRequest('Invalid X-Object-Bytes header')
1140 1495b972 Antony Chazapis
    
1141 ab2e317e Antony Chazapis
    if src_object:
1142 39593b2b Giorgos Verigakis
        if offset % request.backend.block_size == 0:
1143 ab2e317e Antony Chazapis
            # Update the hashes only.
1144 ab2e317e Antony Chazapis
            sbi = 0
1145 ab2e317e Antony Chazapis
            while length > 0:
1146 39593b2b Giorgos Verigakis
                bi = int(offset / request.backend.block_size)
1147 39593b2b Giorgos Verigakis
                bl = min(length, request.backend.block_size)
1148 ab2e317e Antony Chazapis
                if bi < len(hashmap):
1149 39593b2b Giorgos Verigakis
                    if bl == request.backend.block_size:
1150 ab2e317e Antony Chazapis
                        hashmap[bi] = src_hashmap[sbi]
1151 ab2e317e Antony Chazapis
                    else:
1152 39593b2b Giorgos Verigakis
                        data = request.backend.get_block(src_hashmap[sbi])
1153 39593b2b Giorgos Verigakis
                        hashmap[bi] = request.backend.update_block(hashmap[bi],
1154 39593b2b Giorgos Verigakis
                                                                data[:bl], 0)
1155 ab2e317e Antony Chazapis
                else:
1156 ab2e317e Antony Chazapis
                    hashmap.append(src_hashmap[sbi])
1157 ab2e317e Antony Chazapis
                offset += bl
1158 ab2e317e Antony Chazapis
                length -= bl
1159 ab2e317e Antony Chazapis
                sbi += 1
1160 ab2e317e Antony Chazapis
        else:
1161 ab2e317e Antony Chazapis
            data = ''
1162 ab2e317e Antony Chazapis
            sbi = 0
1163 ab2e317e Antony Chazapis
            while length > 0:
1164 39593b2b Giorgos Verigakis
                data += request.backend.get_block(src_hashmap[sbi])
1165 39593b2b Giorgos Verigakis
                if length < request.backend.block_size:
1166 ab2e317e Antony Chazapis
                    data = data[:length]
1167 39593b2b Giorgos Verigakis
                bytes = put_object_block(request, hashmap, data, offset)
1168 ab2e317e Antony Chazapis
                offset += bytes
1169 ab2e317e Antony Chazapis
                data = data[bytes:]
1170 ab2e317e Antony Chazapis
                length -= bytes
1171 ab2e317e Antony Chazapis
                sbi += 1
1172 ab2e317e Antony Chazapis
    else:
1173 ab2e317e Antony Chazapis
        data = ''
1174 39593b2b Giorgos Verigakis
        for d in socket_read_iterator(request, length,
1175 39593b2b Giorgos Verigakis
                                        request.backend.block_size):
1176 ab2e317e Antony Chazapis
            # TODO: Raise 408 (Request Timeout) if this takes too long.
1177 ab2e317e Antony Chazapis
            # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
1178 ab2e317e Antony Chazapis
            data += d
1179 39593b2b Giorgos Verigakis
            bytes = put_object_block(request, hashmap, data, offset)
1180 ab2e317e Antony Chazapis
            offset += bytes
1181 ab2e317e Antony Chazapis
            data = data[bytes:]
1182 ab2e317e Antony Chazapis
        if len(data) > 0:
1183 39593b2b Giorgos Verigakis
            put_object_block(request, hashmap, data, offset)
1184 cfe6939d Antony Chazapis
    
1185 cfe6939d Antony Chazapis
    if offset > size:
1186 cfe6939d Antony Chazapis
        size = offset
1187 1495b972 Antony Chazapis
    if dest_bytes is not None and dest_bytes < size:
1188 1495b972 Antony Chazapis
        size = dest_bytes
1189 39593b2b Giorgos Verigakis
        hashmap = hashmap[:(int((size - 1) / request.backend.block_size) + 1)]
1190 33b4e4a6 Antony Chazapis
    checksum = hashmap_md5(request, hashmap, size)
1191 cfe6939d Antony Chazapis
    try:
1192 61efb530 Antony Chazapis
        version_id = request.backend.update_object_hashmap(request.user_uniq,
1193 71dbc012 Antony Chazapis
                        v_account, v_container, v_object, size, prev_meta['type'],
1194 33b4e4a6 Antony Chazapis
                        hashmap, checksum, 'pithos', meta, replace, permissions)
1195 cca6c617 Antony Chazapis
    except NotAllowedError:
1196 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
1197 cfe6939d Antony Chazapis
    except NameError:
1198 cfe6939d Antony Chazapis
        raise ItemNotFound('Container does not exist')
1199 3436eeb0 Antony Chazapis
    except ValueError:
1200 3436eeb0 Antony Chazapis
        raise BadRequest('Invalid sharing header')
1201 5df6c6d1 Antony Chazapis
    except QuotaError:
1202 5df6c6d1 Antony Chazapis
        raise RequestEntityTooLarge('Quota exceeded')
1203 e0f916bb Antony Chazapis
    if public is not None:
1204 e0f916bb Antony Chazapis
        try:
1205 61efb530 Antony Chazapis
            request.backend.update_object_public(request.user_uniq, v_account,
1206 39593b2b Giorgos Verigakis
                                                v_container, v_object, public)
1207 e0f916bb Antony Chazapis
        except NotAllowedError:
1208 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
1209 e0f916bb Antony Chazapis
        except NameError:
1210 e0f916bb Antony Chazapis
            raise ItemNotFound('Object does not exist')
1211 3436eeb0 Antony Chazapis
    
1212 e9285524 Antony Chazapis
    response = HttpResponse(status=204)
1213 33b4e4a6 Antony Chazapis
    response['ETag'] = checksum
1214 7dd293a0 Antony Chazapis
    response['X-Object-Version'] = version_id
1215 e9285524 Antony Chazapis
    return response
1216 b956618e Antony Chazapis
1217 b956618e Antony Chazapis
@api_method('DELETE')
1218 b956618e Antony Chazapis
def object_delete(request, v_account, v_container, v_object):
1219 b956618e Antony Chazapis
    # Normal Response Codes: 204
1220 08de868d Antony Chazapis
    # Error Response Codes: internalServerError (500),
1221 b956618e Antony Chazapis
    #                       itemNotFound (404),
1222 297513ba Antony Chazapis
    #                       forbidden (403),
1223 b956618e Antony Chazapis
    #                       badRequest (400)
1224 b956618e Antony Chazapis
    
1225 bbd20b55 Antony Chazapis
    until = get_int_parameter(request.GET.get('until'))
1226 b956618e Antony Chazapis
    try:
1227 61efb530 Antony Chazapis
        request.backend.delete_object(request.user_uniq, v_account, v_container,
1228 39593b2b Giorgos Verigakis
                                        v_object, until)
1229 cca6c617 Antony Chazapis
    except NotAllowedError:
1230 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
1231 b956618e Antony Chazapis
    except NameError:
1232 b956618e Antony Chazapis
        raise ItemNotFound('Object does not exist')
1233 b956618e Antony Chazapis
    return HttpResponse(status=204)
1234 b956618e Antony Chazapis
1235 b956618e Antony Chazapis
@api_method()
1236 b956618e Antony Chazapis
def method_not_allowed(request):
1237 b956618e Antony Chazapis
    raise BadRequest('Method not allowed')