functions.py 50.9 KB
Newer Older
Antony Chazapis's avatar
Antony Chazapis committed
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
Antony Chazapis's avatar
Antony Chazapis committed
3 4 5
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
6
#
Antony Chazapis's avatar
Antony Chazapis committed
7 8 9
#   1. Redistributions of source code must retain the above
#      copyright notice, this list of conditions and the following
#      disclaimer.
10
#
Antony Chazapis's avatar
Antony Chazapis committed
11 12 13 14
#   2. Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials
#      provided with the distribution.
15
#
Antony Chazapis's avatar
Antony Chazapis committed
16 17 18 19 20 21 22 23 24 25 26 27
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
28
#
Antony Chazapis's avatar
Antony Chazapis committed
29 30 31 32 33
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.

Antony Chazapis's avatar
Antony Chazapis committed
34
from xml.dom import minidom
root's avatar
Fix.  
root committed
35
from urllib import unquote
36

37
from django.conf import settings
38 39 40 41
from django.http import HttpResponse
from django.template.loader import render_to_string
from django.utils import simplejson as json
from django.utils.http import parse_etags
42
from django.utils.encoding import smart_str
43
from django.views.decorators.csrf import csrf_exempt
44

Antony Chazapis's avatar
Antony Chazapis committed
45
from synnefo.lib.astakos import get_user
46

47 48
from pithos.api.faults import (
    Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound, Conflict,
49
    LengthRequired, PreconditionFailed, RequestEntityTooLarge, RangeNotSatisfiable, UnprocessableEntity)
50 51
from pithos.api.util import (
    json_encode_decimal, rename_meta_key, format_header_key, printable_header_dict,
52 53 54 55
    get_account_headers, put_account_headers, get_container_headers, put_container_headers, get_object_headers,
    put_object_headers, update_manifest_meta, update_sharing_meta, update_public_meta,
    validate_modification_preconditions, validate_matching_preconditions, split_container_object_string,
    copy_or_move_object, get_int_parameter, get_content_length, get_content_range, socket_read_iterator,
56
    SaveToBackendHandler, object_data_response, put_object_block, hashmap_md5, simple_list_response, api_method)
57
from pithos.api.settings import UPDATE_MD5
58

59 60
from pithos.backends.base import NotAllowedError, QuotaError, ContainerNotEmpty, ItemNotExists, VersionNotExists

Antony Chazapis's avatar
Antony Chazapis committed
61 62
from pithos.backends.filter import parse_filters

Antony Chazapis's avatar
Antony Chazapis committed
63 64 65
import logging
import hashlib

66 67 68 69

logger = logging.getLogger(__name__)


70
@csrf_exempt
71 72
def top_demux(request):
    if request.method == 'GET':
73 74 75 76 77 78 79 80
        try:
            request.GET['X-Auth-Token']
        except KeyError:
            try:
                request.META['HTTP_X_AUTH_TOKEN']
            except KeyError:
                return authenticate(request)
        return account_list(request)
81 82 83
    else:
        return method_not_allowed(request)

84

85
@csrf_exempt
86 87 88 89 90
def account_demux(request, v_account):
    if request.method == 'HEAD':
        return account_meta(request, v_account)
    elif request.method == 'POST':
        return account_update(request, v_account)
91 92
    elif request.method == 'GET':
        return container_list(request, v_account)
93 94 95
    else:
        return method_not_allowed(request)

96

97
@csrf_exempt
98 99 100 101 102 103 104 105 106
def container_demux(request, v_account, v_container):
    if request.method == 'HEAD':
        return container_meta(request, v_account, v_container)
    elif request.method == 'PUT':
        return container_create(request, v_account, v_container)
    elif request.method == 'POST':
        return container_update(request, v_account, v_container)
    elif request.method == 'DELETE':
        return container_delete(request, v_account, v_container)
107 108
    elif request.method == 'GET':
        return object_list(request, v_account, v_container)
109 110 111
    else:
        return method_not_allowed(request)

112

113
@csrf_exempt
114
def object_demux(request, v_account, v_container, v_object):
115
    # Helper to avoid placing the token in the URL when loading objects from a browser.
116 117 118 119 120 121 122 123 124 125 126
    if request.method == 'HEAD':
        return object_meta(request, v_account, v_container, v_object)
    elif request.method == 'GET':
        return object_read(request, v_account, v_container, v_object)
    elif request.method == 'PUT':
        return object_write(request, v_account, v_container, v_object)
    elif request.method == 'COPY':
        return object_copy(request, v_account, v_container, v_object)
    elif request.method == 'MOVE':
        return object_move(request, v_account, v_container, v_object)
    elif request.method == 'POST':
127 128
        if request.META.get('CONTENT_TYPE', '').startswith('multipart/form-data'):
            return object_write_form(request, v_account, v_container, v_object)
129 130 131 132 133 134
        return object_update(request, v_account, v_container, v_object)
    elif request.method == 'DELETE':
        return object_delete(request, v_account, v_container, v_object)
    else:
        return method_not_allowed(request)

135

136
@api_method('GET', user_required=False)
137 138
def authenticate(request):
    # Normal Response Codes: 204
139
    # Error Response Codes: internalServerError (500),
140
    #                       forbidden (403),
141
    #                       badRequest (400)
142

143 144 145 146 147
    x_auth_user = request.META.get('HTTP_X_AUTH_USER')
    x_auth_key = request.META.get('HTTP_X_AUTH_KEY')
    if not x_auth_user or not x_auth_key:
        raise BadRequest('Missing X-Auth-User or X-Auth-Key header')
    response = HttpResponse(status=204)
148

149 150 151
    uri = request.build_absolute_uri()
    if '?' in uri:
        uri = uri[:uri.find('?')]
152

153
    response['X-Auth-Token'] = x_auth_key
154 155
    response['X-Storage-Url'] = uri + ('' if uri.endswith('/')
                                       else '/') + x_auth_user
156 157
    return response

158

159 160 161
@api_method('GET', format_allowed=True)
def account_list(request):
    # Normal Response Codes: 200, 204
162
    # Error Response Codes: internalServerError (500),
163 164
    #                       badRequest (400)
    response = HttpResponse()
165

166 167 168 169
    marker = request.GET.get('marker')
    limit = get_int_parameter(request.GET.get('limit'))
    if not limit:
        limit = 10000
170

171
    accounts = request.backend.list_accounts(request.user_uniq, marker, limit)
172

173 174 175 176 177 178 179 180
    if request.serialization == 'text':
        if len(accounts) == 0:
            # The cloudfiles python bindings expect 200 if json/xml.
            response.status_code = 204
            return response
        response.status_code = 200
        response.content = '\n'.join(accounts) + '\n'
        return response
181

182 183
    account_meta = []
    for x in accounts:
184 185
        if x == request.user_uniq:
            continue
186
        try:
187 188
            meta = request.backend.get_account_meta(
                request.user_uniq, x, 'pithos', include_user_defined=False)
189
            groups = request.backend.get_account_groups(request.user_uniq, x)
190
        except NotAllowedError:
191
            raise Forbidden('Not allowed')
192
        else:
193
            rename_meta_key(meta, 'modified', 'last_modified')
194 195
            rename_meta_key(
                meta, 'until_timestamp', 'x_account_until_timestamp')
196
            if groups:
197 198
                meta['X-Account-Group'] = printable_header_dict(
                    dict([(k, ','.join(v)) for k, v in groups.iteritems()]))
199 200 201
            account_meta.append(printable_header_dict(meta))
    if request.serialization == 'xml':
        data = render_to_string('accounts.xml', {'accounts': account_meta})
202
    elif request.serialization == 'json':
203 204 205 206 207
        data = json.dumps(account_meta)
    response.status_code = 200
    response.content = data
    return response

208

209 210 211
@api_method('HEAD')
def account_meta(request, v_account):
    # Normal Response Codes: 204
212
    # Error Response Codes: internalServerError (500),
213
    #                       forbidden (403),
214
    #                       badRequest (400)
215

Antony Chazapis's avatar
Antony Chazapis committed
216
    until = get_int_parameter(request.GET.get('until'))
Antony Chazapis's avatar
Antony Chazapis committed
217
    try:
218 219 220 221 222 223
        meta = request.backend.get_account_meta(
            request.user_uniq, v_account, 'pithos', until)
        groups = request.backend.get_account_groups(
            request.user_uniq, v_account)
        policy = request.backend.get_account_policy(
            request.user_uniq, v_account)
Antony Chazapis's avatar
Antony Chazapis committed
224
    except NotAllowedError:
225
        raise Forbidden('Not allowed')
226

227
    validate_modification_preconditions(request, meta)
228

229
    response = HttpResponse(status=204)
230
    put_account_headers(response, meta, groups, policy)
231 232
    return response

233

234 235 236
@api_method('POST')
def account_update(request, v_account):
    # Normal Response Codes: 202
237
    # Error Response Codes: internalServerError (500),
238
    #                       forbidden (403),
239
    #                       badRequest (400)
240

241
    meta, groups = get_account_headers(request)
242 243
    replace = True
    if 'update' in request.GET:
244
        replace = False
245 246
    if groups:
        try:
247
            request.backend.update_account_groups(request.user_uniq, v_account,
248
                                                  groups, replace)
249
        except NotAllowedError:
250
            raise Forbidden('Not allowed')
251 252
        except ValueError:
            raise BadRequest('Invalid groups header')
253 254
    if meta or replace:
        try:
255 256
            request.backend.update_account_meta(request.user_uniq, v_account,
                                                'pithos', meta, replace)
257
        except NotAllowedError:
258
            raise Forbidden('Not allowed')
259 260
    return HttpResponse(status=202)

261

262 263 264
@api_method('GET', format_allowed=True)
def container_list(request, v_account):
    # Normal Response Codes: 200, 204
265
    # Error Response Codes: internalServerError (500),
266
    #                       itemNotFound (404),
267
    #                       forbidden (403),
268
    #                       badRequest (400)
269

Antony Chazapis's avatar
Antony Chazapis committed
270
    until = get_int_parameter(request.GET.get('until'))
Antony Chazapis's avatar
Antony Chazapis committed
271
    try:
272 273 274 275 276 277
        meta = request.backend.get_account_meta(
            request.user_uniq, v_account, 'pithos', until)
        groups = request.backend.get_account_groups(
            request.user_uniq, v_account)
        policy = request.backend.get_account_policy(
            request.user_uniq, v_account)
Antony Chazapis's avatar
Antony Chazapis committed
278
    except NotAllowedError:
279
        raise Forbidden('Not allowed')
280

281
    validate_modification_preconditions(request, meta)
282

283
    response = HttpResponse()
284
    put_account_headers(response, meta, groups, policy)
285

286
    marker = request.GET.get('marker')
287 288 289
    limit = get_int_parameter(request.GET.get('limit'))
    if not limit:
        limit = 10000
290

291 292 293
    shared = False
    if 'shared' in request.GET:
        shared = True
294 295 296
    public = False
    if 'public' in request.GET:
        public = True
297

298
    try:
299 300 301
        containers = request.backend.list_containers(
            request.user_uniq, v_account,
            marker, limit, shared, until, public)
Antony Chazapis's avatar
Antony Chazapis committed
302
    except NotAllowedError:
303
        raise Forbidden('Not allowed')
304 305
    except NameError:
        containers = []
306

307 308 309 310 311 312
    if request.serialization == 'text':
        if len(containers) == 0:
            # The cloudfiles python bindings expect 200 if json/xml.
            response.status_code = 204
            return response
        response.status_code = 200
313
        response.content = '\n'.join(containers) + '\n'
314
        return response
315

316 317
    container_meta = []
    for x in containers:
318
        try:
319 320 321
            meta = request.backend.get_container_meta(
                request.user_uniq, v_account,
                x, 'pithos', until, include_user_defined=False)
322
            policy = request.backend.get_container_policy(request.user_uniq,
323
                                                          v_account, x)
324
        except NotAllowedError:
325
            raise Forbidden('Not allowed')
326 327 328
        except NameError:
            pass
        else:
329
            rename_meta_key(meta, 'modified', 'last_modified')
330 331
            rename_meta_key(
                meta, 'until_timestamp', 'x_container_until_timestamp')
332
            if policy:
333 334
                meta['X-Container-Policy'] = printable_header_dict(
                    dict([(k, v) for k, v in policy.iteritems()]))
335
            container_meta.append(printable_header_dict(meta))
336
    if request.serialization == 'xml':
337 338 339
        data = render_to_string('containers.xml', {'account':
                                v_account, 'containers': container_meta})
    elif request.serialization == 'json':
340 341 342 343 344
        data = json.dumps(container_meta)
    response.status_code = 200
    response.content = data
    return response

345

346 347 348
@api_method('HEAD')
def container_meta(request, v_account, v_container):
    # Normal Response Codes: 204
349
    # Error Response Codes: internalServerError (500),
350
    #                       itemNotFound (404),
351
    #                       forbidden (403),
352
    #                       badRequest (400)
353

Antony Chazapis's avatar
Antony Chazapis committed
354
    until = get_int_parameter(request.GET.get('until'))
355
    try:
356
        meta = request.backend.get_container_meta(request.user_uniq, v_account,
357
                                                  v_container, 'pithos', until)
358
        meta['object_meta'] = request.backend.list_container_meta(request.user_uniq,
359 360 361 362
                                                                  v_account, v_container, 'pithos', until)
        policy = request.backend.get_container_policy(
            request.user_uniq, v_account,
            v_container)
Antony Chazapis's avatar
Antony Chazapis committed
363
    except NotAllowedError:
364
        raise Forbidden('Not allowed')
365
    except ItemNotExists:
366
        raise ItemNotFound('Container does not exist')
367

368
    validate_modification_preconditions(request, meta)
369

370
    response = HttpResponse(status=204)
371
    put_container_headers(request, response, meta, policy)
372 373
    return response

374

375 376 377
@api_method('PUT')
def container_create(request, v_account, v_container):
    # Normal Response Codes: 201, 202
378
    # Error Response Codes: internalServerError (500),
379
    #                       itemNotFound (404),
380
    #                       forbidden (403),
381
    #                       badRequest (400)
382

Antony Chazapis's avatar
Antony Chazapis committed
383
    meta, policy = get_container_headers(request)
384

385
    try:
386 387
        request.backend.put_container(
            request.user_uniq, v_account, v_container, policy)
388
        ret = 201
Antony Chazapis's avatar
Antony Chazapis committed
389
    except NotAllowedError:
390
        raise Forbidden('Not allowed')
391 392
    except ValueError:
        raise BadRequest('Invalid policy header')
393 394
    except NameError:
        ret = 202
395

396
    if ret == 202 and policy:
Antony Chazapis's avatar
Antony Chazapis committed
397
        try:
398 399 400
            request.backend.update_container_policy(
                request.user_uniq, v_account,
                v_container, policy, replace=False)
401
        except NotAllowedError:
402
            raise Forbidden('Not allowed')
403
        except ItemNotExists:
404 405 406 407 408
            raise ItemNotFound('Container does not exist')
        except ValueError:
            raise BadRequest('Invalid policy header')
    if meta:
        try:
409
            request.backend.update_container_meta(request.user_uniq, v_account,
410
                                                  v_container, 'pithos', meta, replace=False)
Antony Chazapis's avatar
Antony Chazapis committed
411
        except NotAllowedError:
412
            raise Forbidden('Not allowed')
413
        except ItemNotExists:
Antony Chazapis's avatar
Antony Chazapis committed
414
            raise ItemNotFound('Container does not exist')
415

416 417
    return HttpResponse(status=ret)

418

419
@api_method('POST', format_allowed=True)
420 421
def container_update(request, v_account, v_container):
    # Normal Response Codes: 202
422
    # Error Response Codes: internalServerError (500),
423
    #                       itemNotFound (404),
424
    #                       forbidden (403),
425
    #                       badRequest (400)
426

Antony Chazapis's avatar
Antony Chazapis committed
427
    meta, policy = get_container_headers(request)
428 429 430
    replace = True
    if 'update' in request.GET:
        replace = False
Antony Chazapis's avatar
Antony Chazapis committed
431 432
    if policy:
        try:
433 434 435
            request.backend.update_container_policy(
                request.user_uniq, v_account,
                v_container, policy, replace)
Antony Chazapis's avatar
Antony Chazapis committed
436
        except NotAllowedError:
437
            raise Forbidden('Not allowed')
438
        except ItemNotExists:
Antony Chazapis's avatar
Antony Chazapis committed
439 440 441
            raise ItemNotFound('Container does not exist')
        except ValueError:
            raise BadRequest('Invalid policy header')
442 443
    if meta or replace:
        try:
444
            request.backend.update_container_meta(request.user_uniq, v_account,
445
                                                  v_container, 'pithos', meta, replace)
446
        except NotAllowedError:
447
            raise Forbidden('Not allowed')
448
        except ItemNotExists:
449
            raise ItemNotFound('Container does not exist')
450

451 452
    content_length = -1
    if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
453 454
        content_length = get_int_parameter(
            request.META.get('CONTENT_LENGTH', 0))
455
    content_type = request.META.get('CONTENT_TYPE')
456 457 458
    hashmap = []
    if content_type and content_type == 'application/octet-stream' and content_length != 0:
        for data in socket_read_iterator(request, content_length,
459
                                         request.backend.block_size):
460 461 462
            # TODO: Raise 408 (Request Timeout) if this takes too long.
            # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
            hashmap.append(request.backend.put_block(data))
463

464 465
    response = HttpResponse(status=202)
    if hashmap:
466
        response.content = simple_list_response(request, hashmap)
467
    return response
468

469

470 471 472
@api_method('DELETE')
def container_delete(request, v_account, v_container):
    # Normal Response Codes: 204
473
    # Error Response Codes: internalServerError (500),
474 475
    #                       conflict (409),
    #                       itemNotFound (404),
476
    #                       forbidden (403),
477
    #                       badRequest (400)
478

479
    until = get_int_parameter(request.GET.get('until'))
480

481
    delimiter = request.GET.get('delimiter')
482

483
    try:
484 485 486
        request.backend.delete_container(
            request.user_uniq, v_account, v_container,
            until, delimiter=delimiter)
Antony Chazapis's avatar
Antony Chazapis committed
487
    except NotAllowedError:
488
        raise Forbidden('Not allowed')
489
    except ItemNotExists:
490
        raise ItemNotFound('Container does not exist')
491
    except ContainerNotEmpty:
492 493 494
        raise Conflict('Container is not empty')
    return HttpResponse(status=204)

495

496 497 498
@api_method('GET', format_allowed=True)
def object_list(request, v_account, v_container):
    # Normal Response Codes: 200, 204
499
    # Error Response Codes: internalServerError (500),
500
    #                       itemNotFound (404),
501
    #                       forbidden (403),
502
    #                       badRequest (400)
503

Antony Chazapis's avatar
Antony Chazapis committed
504
    until = get_int_parameter(request.GET.get('until'))
505
    try:
506
        meta = request.backend.get_container_meta(request.user_uniq, v_account,
507
                                                  v_container, 'pithos', until)
508
        meta['object_meta'] = request.backend.list_container_meta(request.user_uniq,
509 510 511 512
                                                                  v_account, v_container, 'pithos', until)
        policy = request.backend.get_container_policy(
            request.user_uniq, v_account,
            v_container)
Antony Chazapis's avatar
Antony Chazapis committed
513
    except NotAllowedError:
514
        raise Forbidden('Not allowed')
515
    except ItemNotExists:
516
        raise ItemNotFound('Container does not exist')
517

518
    validate_modification_preconditions(request, meta)
519

520
    response = HttpResponse()
521
    put_container_headers(request, response, meta, policy)
522

523 524 525
    path = request.GET.get('path')
    prefix = request.GET.get('prefix')
    delimiter = request.GET.get('delimiter')
526

527 528 529 530 531 532
    # Path overrides prefix and delimiter.
    virtual = True
    if path:
        prefix = path
        delimiter = '/'
        virtual = False
533

534
    # Naming policy.
535
    if prefix and delimiter and not prefix.endswith(delimiter):
536 537 538 539
        prefix = prefix + delimiter
    if not prefix:
        prefix = ''
    prefix = prefix.lstrip('/')
540

541
    marker = request.GET.get('marker')
542 543 544
    limit = get_int_parameter(request.GET.get('limit'))
    if not limit:
        limit = 10000
545

546 547
    keys = request.GET.get('meta')
    if keys:
548 549
        keys = [smart_str(x.strip()) for x in keys.split(',')
                if x.strip() != '']
550 551 552
        included, excluded, opers = parse_filters(keys)
        keys = []
        keys += [format_header_key('X-Object-Meta-' + x) for x in included]
553 554 555 556
        keys += ['!' + format_header_key('X-Object-Meta-' + x)
                 for x in excluded]
        keys += ['%s%s%s' % (format_header_key(
            'X-Object-Meta-' + k), o, v) for k, o, v in opers]
557 558
    else:
        keys = []
559

560 561 562
    shared = False
    if 'shared' in request.GET:
        shared = True
563 564 565
    public = False
    if 'public' in request.GET:
        public = True
566

567
    if request.serialization == 'text':
568
        try:
569 570 571 572 573
            objects = request.backend.list_objects(
                request.user_uniq, v_account,
                v_container, prefix, delimiter, marker,
                limit, virtual, 'pithos', keys, shared,
                until, None, public)
574 575
        except NotAllowedError:
            raise Forbidden('Not allowed')
576
        except ItemNotExists:
577
            raise ItemNotFound('Container does not exist')
578

579 580 581 582 583
        if len(objects) == 0:
            # The cloudfiles python bindings expect 200 if json/xml.
            response.status_code = 204
            return response
        response.status_code = 200
584
        response.content = '\n'.join([x[0] for x in objects]) + '\n'
585
        return response
586

587
    try:
588 589 590 591
        objects = request.backend.list_object_meta(
            request.user_uniq, v_account,
            v_container, prefix, delimiter, marker,
            limit, virtual, 'pithos', keys, shared, until, None, public)
592 593 594 595 596
        object_permissions = {}
        object_public = {}
        if until is None:
            name_idx = len('/'.join((v_account, v_container, '')))
            for x in request.backend.list_object_permissions(request.user_uniq,
597
                                                             v_account, v_container, prefix):
598 599
                object = x[name_idx:]
                object_permissions[object] = request.backend.get_object_permissions(
600
                    request.user_uniq, v_account, v_container, object)
601
            for k, v in request.backend.list_object_public(request.user_uniq,
602
                                                           v_account, v_container, prefix).iteritems():
603
                object_public[k[name_idx:]] = v
604 605
    except NotAllowedError:
        raise Forbidden('Not allowed')
606
    except ItemNotExists:
607
        raise ItemNotFound('Container does not exist')
608

609
    object_meta = []
610 611
    for meta in objects:
        if len(meta) == 1:
612
            # Virtual objects/directories.
613
            object_meta.append(meta)
614
        else:
615 616
            rename_meta_key(
                meta, 'hash', 'x_object_hash')  # Will be replaced by checksum.
617 618 619 620 621
            rename_meta_key(meta, 'checksum', 'hash')
            rename_meta_key(meta, 'type', 'content_type')
            rename_meta_key(meta, 'uuid', 'x_object_uuid')
            if until is not None and 'modified' in meta:
                del(meta['modified'])
Antony Chazapis's avatar
Antony Chazapis committed
622
            else:
623
                rename_meta_key(meta, 'modified', 'last_modified')
624 625
            rename_meta_key(meta, 'modified_by', 'x_object_modified_by')
            rename_meta_key(meta, 'version', 'x_object_version')
626 627
            rename_meta_key(
                meta, 'version_timestamp', 'x_object_version_timestamp')
628 629
            permissions = object_permissions.get(meta['name'], None)
            if permissions:
630 631
                update_sharing_meta(request, permissions, v_account,
                                    v_container, meta['name'], meta)
632 633 634
            public = object_public.get(meta['name'], None)
            if public:
                update_public_meta(public, meta)
635
            object_meta.append(printable_header_dict(meta))
636
    if request.serialization == 'xml':
637 638 639
        data = render_to_string(
            'objects.xml', {'container': v_container, 'objects': object_meta})
    elif request.serialization == 'json':
640
        data = json.dumps(object_meta, default=json_encode_decimal)
641 642 643 644
    response.status_code = 200
    response.content = data
    return response

645

646 647 648
@api_method('HEAD')
def object_meta(request, v_account, v_container, v_object):
    # Normal Response Codes: 204
649
    # Error Response Codes: internalServerError (500),
650
    #                       itemNotFound (404),
651
    #                       forbidden (403),
652
    #                       badRequest (400)
653

Antony Chazapis's avatar
Antony Chazapis committed
654
    version = request.GET.get('version')
655
    try:
656
        meta = request.backend.get_object_meta(request.user_uniq, v_account,
657
                                               v_container, v_object, 'pithos', version)
658
        if version is None:
659 660 661 662 663 664
            permissions = request.backend.get_object_permissions(
                request.user_uniq,
                v_account, v_container, v_object)
            public = request.backend.get_object_public(
                request.user_uniq, v_account,
                v_container, v_object)
665 666
        else:
            permissions = None
667
            public = None
Antony Chazapis's avatar
Antony Chazapis committed
668
    except NotAllowedError:
669
        raise Forbidden('Not allowed')
670
    except ItemNotExists:
671
        raise ItemNotFound('Object does not exist')
672
    except VersionNotExists:
673
        raise ItemNotFound('Version does not exist')
674

Antony Chazapis's avatar
Antony Chazapis committed
675
    update_manifest_meta(request, v_account, meta)
676 677
    update_sharing_meta(
        request, permissions, v_account, v_container, v_object, meta)
678
    update_public_meta(public, meta)
679

680 681 682 683 684 685
    # Evaluate conditions.
    validate_modification_preconditions(request, meta)
    try:
        validate_matching_preconditions(request, meta)
    except NotModified:
        response = HttpResponse(status=304)
686
        response['ETag'] = meta['checksum']
687
        return response
688

Antony Chazapis's avatar
Antony Chazapis committed
689
    response = HttpResponse(status=200)
690
    put_object_headers(response, meta)
691 692
    return response

693

694
@api_method('GET', format_allowed=True)
695 696
def object_read(request, v_account, v_container, v_object):
    # Normal Response Codes: 200, 206
697
    # Error Response Codes: internalServerError (500),
698 699 700
    #                       rangeNotSatisfiable (416),
    #                       preconditionFailed (412),
    #                       itemNotFound (404),
701
    #                       forbidden (403),
702 703
    #                       badRequest (400),
    #                       notModified (304)
704

Antony Chazapis's avatar
Antony Chazapis committed
705
    version = request.GET.get('version')
706

707
    # Reply with the version list. Do this first, as the object may be deleted.
Antony Chazapis's avatar
Antony Chazapis committed
708
    if version == 'list':
709 710
        if request.serialization == 'text':
            raise BadRequest('No format specified for version list.')
711

Antony Chazapis's avatar
Antony Chazapis committed
712
        try:
713
            v = request.backend.list_versions(request.user_uniq, v_account,
714
                                              v_container, v_object)
Antony Chazapis's avatar
Antony Chazapis committed
715
        except NotAllowedError:
716
            raise Forbidden('Not allowed')
Antony Chazapis's avatar
Antony Chazapis committed
717
        d = {'versions': v}
718 719 720
        if request.serialization == 'xml':
            d['object'] = v_object
            data = render_to_string('versions.xml', d)
721
        elif request.serialization == 'json':
722
            data = json.dumps(d, default=json_encode_decimal)
723

724 725 726
        response = HttpResponse(data, status=200)
        response['Content-Length'] = len(data)
        return response
727

728
    try:
729
        meta = request.backend.get_object_meta(request.user_uniq, v_account,
730
                                               v_container, v_object, 'pithos', version)
731
        if version is None:
732 733 734 735 736 737
            permissions = request.backend.get_object_permissions(
                request.user_uniq,
                v_account, v_container, v_object)
            public = request.backend.get_object_public(
                request.user_uniq, v_account,
                v_container, v_object)
738 739
        else:
            permissions = None
740
            public = None
Antony Chazapis's avatar
Antony Chazapis committed
741
    except NotAllowedError:
742
        raise Forbidden('Not allowed')
743
    except ItemNotExists:
744
        raise ItemNotFound('Object does not exist')
745
    except VersionNotExists:
746
        raise ItemNotFound('Version does not exist')
747