Revision 2715ade4 snf-pithos-app/pithos/api/functions.py

b/snf-pithos-app/pithos/api/functions.py
1 1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
# 
2
#
3 3
# Redistribution and use in source and binary forms, with or
4 4
# without modification, are permitted provided that the following
5 5
# conditions are met:
6
# 
6
#
7 7
#   1. Redistributions of source code must retain the above
8 8
#      copyright notice, this list of conditions and the following
9 9
#      disclaimer.
10
# 
10
#
11 11
#   2. Redistributions in binary form must reproduce the above
12 12
#      copyright notice, this list of conditions and the following
13 13
#      disclaimer in the documentation and/or other materials
14 14
#      provided with the distribution.
15
# 
15
#
16 16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
......
25 25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 27
# POSSIBILITY OF SUCH DAMAGE.
28
# 
28
#
29 29
# The views and conclusions contained in the software and
30 30
# documentation are those of the authors and should not be
31 31
# interpreted as representing official policies, either expressed
......
44 44

  
45 45
from synnefo.lib.astakos import get_user
46 46

  
47
from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound, Conflict,
47
from pithos.api.faults import (
48
    Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound, Conflict,
48 49
    LengthRequired, PreconditionFailed, RequestEntityTooLarge, RangeNotSatisfiable, UnprocessableEntity)
49
from pithos.api.util import (json_encode_decimal, rename_meta_key, format_header_key, printable_header_dict,
50
from pithos.api.util import (
51
    json_encode_decimal, rename_meta_key, format_header_key, printable_header_dict,
50 52
    get_account_headers, put_account_headers, get_container_headers, put_container_headers, get_object_headers,
51 53
    put_object_headers, update_manifest_meta, update_sharing_meta, update_public_meta,
52 54
    validate_modification_preconditions, validate_matching_preconditions, split_container_object_string,
......
68 70
@csrf_exempt
69 71
def top_demux(request):
70 72
    if request.method == 'GET':
71
    	try:
72
    		request.GET['X-Auth-Token']
73
    	except KeyError:
74
    		try:
75
    			request.META['HTTP_X_AUTH_TOKEN']
76
    		except KeyError:
77
    			return authenticate(request)
78
    	return account_list(request)
73
        try:
74
            request.GET['X-Auth-Token']
75
        except KeyError:
76
            try:
77
                request.META['HTTP_X_AUTH_TOKEN']
78
            except KeyError:
79
                return authenticate(request)
80
        return account_list(request)
79 81
    else:
80 82
        return method_not_allowed(request)
81 83

  
84

  
82 85
@csrf_exempt
83 86
def account_demux(request, v_account):
84 87
    if request.method == 'HEAD':
......
90 93
    else:
91 94
        return method_not_allowed(request)
92 95

  
96

  
93 97
@csrf_exempt
94 98
def container_demux(request, v_account, v_container):
95 99
    if request.method == 'HEAD':
......
105 109
    else:
106 110
        return method_not_allowed(request)
107 111

  
112

  
108 113
@csrf_exempt
109 114
def object_demux(request, v_account, v_container, v_object):
110 115
    # Helper to avoid placing the token in the URL when loading objects from a browser.
......
127 132
    else:
128 133
        return method_not_allowed(request)
129 134

  
135

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

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

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

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

  
158

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

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

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

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

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

  
208

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

  
204 216
    until = get_int_parameter(request.GET.get('until'))
205 217
    try:
206
        meta = request.backend.get_account_meta(request.user_uniq, v_account, 'pithos', until)
207
        groups = request.backend.get_account_groups(request.user_uniq, v_account)
208
        policy = request.backend.get_account_policy(request.user_uniq, v_account)
218
        meta = request.backend.get_account_meta(
219
            request.user_uniq, v_account, 'pithos', until)
220
        groups = request.backend.get_account_groups(
221
            request.user_uniq, v_account)
222
        policy = request.backend.get_account_policy(
223
            request.user_uniq, v_account)
209 224
    except NotAllowedError:
210 225
        raise Forbidden('Not allowed')
211
    
226

  
212 227
    validate_modification_preconditions(request, meta)
213
    
228

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

  
233

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

  
225 241
    meta, groups = get_account_headers(request)
226 242
    replace = True
227 243
    if 'update' in request.GET:
......
229 245
    if groups:
230 246
        try:
231 247
            request.backend.update_account_groups(request.user_uniq, v_account,
232
                                                    groups, replace)
248
                                                  groups, replace)
233 249
        except NotAllowedError:
234 250
            raise Forbidden('Not allowed')
235 251
        except ValueError:
......
242 258
            raise Forbidden('Not allowed')
243 259
    return HttpResponse(status=202)
244 260

  
261

  
245 262
@api_method('GET', format_allowed=True)
246 263
def container_list(request, v_account):
247 264
    # Normal Response Codes: 200, 204
......
249 266
    #                       itemNotFound (404),
250 267
    #                       forbidden (403),
251 268
    #                       badRequest (400)
252
    
269

  
253 270
    until = get_int_parameter(request.GET.get('until'))
254 271
    try:
255
        meta = request.backend.get_account_meta(request.user_uniq, v_account, 'pithos', until)
256
        groups = request.backend.get_account_groups(request.user_uniq, v_account)
257
        policy = request.backend.get_account_policy(request.user_uniq, v_account)
272
        meta = request.backend.get_account_meta(
273
            request.user_uniq, v_account, 'pithos', until)
274
        groups = request.backend.get_account_groups(
275
            request.user_uniq, v_account)
276
        policy = request.backend.get_account_policy(
277
            request.user_uniq, v_account)
258 278
    except NotAllowedError:
259 279
        raise Forbidden('Not allowed')
260
    
280

  
261 281
    validate_modification_preconditions(request, meta)
262
    
282

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

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

  
271 291
    shared = False
272 292
    if 'shared' in request.GET:
273 293
        shared = True
274 294
    public = False
275 295
    if 'public' in request.GET:
276 296
        public = True
277
    
297

  
278 298
    try:
279
        containers = request.backend.list_containers(request.user_uniq, v_account,
280
                                                marker, limit, shared, until, public)
299
        containers = request.backend.list_containers(
300
            request.user_uniq, v_account,
301
            marker, limit, shared, until, public)
281 302
    except NotAllowedError:
282 303
        raise Forbidden('Not allowed')
283 304
    except NameError:
284 305
        containers = []
285
    
306

  
286 307
    if request.serialization == 'text':
287 308
        if len(containers) == 0:
288 309
            # The cloudfiles python bindings expect 200 if json/xml.
......
291 312
        response.status_code = 200
292 313
        response.content = '\n'.join(containers) + '\n'
293 314
        return response
294
    
315

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

  
345

  
320 346
@api_method('HEAD')
321 347
def container_meta(request, v_account, v_container):
322 348
    # Normal Response Codes: 204
......
324 350
    #                       itemNotFound (404),
325 351
    #                       forbidden (403),
326 352
    #                       badRequest (400)
327
    
353

  
328 354
    until = get_int_parameter(request.GET.get('until'))
329 355
    try:
330 356
        meta = request.backend.get_container_meta(request.user_uniq, v_account,
331
                                                    v_container, 'pithos', until)
357
                                                  v_container, 'pithos', until)
332 358
        meta['object_meta'] = request.backend.list_container_meta(request.user_uniq,
333
                                                v_account, v_container, 'pithos', until)
334
        policy = request.backend.get_container_policy(request.user_uniq, v_account,
335
                                                        v_container)
359
                                                                  v_account, v_container, 'pithos', until)
360
        policy = request.backend.get_container_policy(
361
            request.user_uniq, v_account,
362
            v_container)
336 363
    except NotAllowedError:
337 364
        raise Forbidden('Not allowed')
338 365
    except ItemNotExists:
339 366
        raise ItemNotFound('Container does not exist')
340
    
367

  
341 368
    validate_modification_preconditions(request, meta)
342
    
369

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

  
374

  
347 375
@api_method('PUT')
348 376
def container_create(request, v_account, v_container):
349 377
    # Normal Response Codes: 201, 202
......
351 379
    #                       itemNotFound (404),
352 380
    #                       forbidden (403),
353 381
    #                       badRequest (400)
354
    
382

  
355 383
    meta, policy = get_container_headers(request)
356
    
384

  
357 385
    try:
358
        request.backend.put_container(request.user_uniq, v_account, v_container, policy)
386
        request.backend.put_container(
387
            request.user_uniq, v_account, v_container, policy)
359 388
        ret = 201
360 389
    except NotAllowedError:
361 390
        raise Forbidden('Not allowed')
......
363 392
        raise BadRequest('Invalid policy header')
364 393
    except NameError:
365 394
        ret = 202
366
    
395

  
367 396
    if ret == 202 and policy:
368 397
        try:
369
            request.backend.update_container_policy(request.user_uniq, v_account,
370
                                            v_container, policy, replace=False)
398
            request.backend.update_container_policy(
399
                request.user_uniq, v_account,
400
                v_container, policy, replace=False)
371 401
        except NotAllowedError:
372 402
            raise Forbidden('Not allowed')
373 403
        except ItemNotExists:
......
377 407
    if meta:
378 408
        try:
379 409
            request.backend.update_container_meta(request.user_uniq, v_account,
380
                                            v_container, 'pithos', meta, replace=False)
410
                                                  v_container, 'pithos', meta, replace=False)
381 411
        except NotAllowedError:
382 412
            raise Forbidden('Not allowed')
383 413
        except ItemNotExists:
384 414
            raise ItemNotFound('Container does not exist')
385
    
415

  
386 416
    return HttpResponse(status=ret)
387 417

  
418

  
388 419
@api_method('POST', format_allowed=True)
389 420
def container_update(request, v_account, v_container):
390 421
    # Normal Response Codes: 202
......
392 423
    #                       itemNotFound (404),
393 424
    #                       forbidden (403),
394 425
    #                       badRequest (400)
395
    
426

  
396 427
    meta, policy = get_container_headers(request)
397 428
    replace = True
398 429
    if 'update' in request.GET:
399 430
        replace = False
400 431
    if policy:
401 432
        try:
402
            request.backend.update_container_policy(request.user_uniq, v_account,
403
                                                v_container, policy, replace)
433
            request.backend.update_container_policy(
434
                request.user_uniq, v_account,
435
                v_container, policy, replace)
404 436
        except NotAllowedError:
405 437
            raise Forbidden('Not allowed')
406 438
        except ItemNotExists:
......
410 442
    if meta or replace:
411 443
        try:
412 444
            request.backend.update_container_meta(request.user_uniq, v_account,
413
                                                    v_container, 'pithos', meta, replace)
445
                                                  v_container, 'pithos', meta, replace)
414 446
        except NotAllowedError:
415 447
            raise Forbidden('Not allowed')
416 448
        except ItemNotExists:
417 449
            raise ItemNotFound('Container does not exist')
418
    
450

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

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

  
469

  
436 470
@api_method('DELETE')
437 471
def container_delete(request, v_account, v_container):
438 472
    # Normal Response Codes: 204
......
441 475
    #                       itemNotFound (404),
442 476
    #                       forbidden (403),
443 477
    #                       badRequest (400)
444
    
478

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

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

  
449 483
    try:
450
        request.backend.delete_container(request.user_uniq, v_account, v_container,
451
                                            until, delimiter=delimiter)
484
        request.backend.delete_container(
485
            request.user_uniq, v_account, v_container,
486
            until, delimiter=delimiter)
452 487
    except NotAllowedError:
453 488
        raise Forbidden('Not allowed')
454 489
    except ItemNotExists:
......
457 492
        raise Conflict('Container is not empty')
458 493
    return HttpResponse(status=204)
459 494

  
495

  
460 496
@api_method('GET', format_allowed=True)
461 497
def object_list(request, v_account, v_container):
462 498
    # Normal Response Codes: 200, 204
......
464 500
    #                       itemNotFound (404),
465 501
    #                       forbidden (403),
466 502
    #                       badRequest (400)
467
    
503

  
468 504
    until = get_int_parameter(request.GET.get('until'))
469 505
    try:
470 506
        meta = request.backend.get_container_meta(request.user_uniq, v_account,
471
                                                    v_container, 'pithos', until)
507
                                                  v_container, 'pithos', until)
472 508
        meta['object_meta'] = request.backend.list_container_meta(request.user_uniq,
473
                                                v_account, v_container, 'pithos', until)
474
        policy = request.backend.get_container_policy(request.user_uniq, v_account,
475
                                                        v_container)
509
                                                                  v_account, v_container, 'pithos', until)
510
        policy = request.backend.get_container_policy(
511
            request.user_uniq, v_account,
512
            v_container)
476 513
    except NotAllowedError:
477 514
        raise Forbidden('Not allowed')
478 515
    except ItemNotExists:
479 516
        raise ItemNotFound('Container does not exist')
480
    
517

  
481 518
    validate_modification_preconditions(request, meta)
482
    
519

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

  
486 523
    path = request.GET.get('path')
487 524
    prefix = request.GET.get('prefix')
488 525
    delimiter = request.GET.get('delimiter')
489
    
526

  
490 527
    # Path overrides prefix and delimiter.
491 528
    virtual = True
492 529
    if path:
493 530
        prefix = path
494 531
        delimiter = '/'
495 532
        virtual = False
496
    
533

  
497 534
    # Naming policy.
498 535
    if prefix and delimiter and not prefix.endswith(delimiter):
499 536
        prefix = prefix + delimiter
500 537
    if not prefix:
501 538
        prefix = ''
502 539
    prefix = prefix.lstrip('/')
503
    
540

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

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

  
520 560
    shared = False
521 561
    if 'shared' in request.GET:
522 562
        shared = True
523 563
    public = False
524 564
    if 'public' in request.GET:
525 565
        public = True
526
    
566

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

  
538 579
        if len(objects) == 0:
539 580
            # The cloudfiles python bindings expect 200 if json/xml.
540 581
            response.status_code = 204
......
544 585
        return response
545 586

  
546 587
    try:
547
        objects = request.backend.list_object_meta(request.user_uniq, v_account,
548
                                    v_container, prefix, delimiter, marker,
549
                                    limit, virtual, 'pithos', keys, shared, until, None, public)
588
        objects = request.backend.list_object_meta(
589
            request.user_uniq, v_account,
590
            v_container, prefix, delimiter, marker,
591
            limit, virtual, 'pithos', keys, shared, until, None, public)
550 592
        object_permissions = {}
551 593
        object_public = {}
552 594
        if until is None:
553 595
            name_idx = len('/'.join((v_account, v_container, '')))
554 596
            for x in request.backend.list_object_permissions(request.user_uniq,
555
                                    v_account, v_container, prefix):
597
                                                             v_account, v_container, prefix):
556 598
                object = x[name_idx:]
557 599
                object_permissions[object] = request.backend.get_object_permissions(
558
                                    request.user_uniq, v_account, v_container, object)
600
                    request.user_uniq, v_account, v_container, object)
559 601
            for k, v in request.backend.list_object_public(request.user_uniq,
560
                                    v_account, v_container, prefix).iteritems():
602
                                                           v_account, v_container, prefix).iteritems():
561 603
                object_public[k[name_idx:]] = v
562 604
    except NotAllowedError:
563 605
        raise Forbidden('Not allowed')
564 606
    except ItemNotExists:
565 607
        raise ItemNotFound('Container does not exist')
566
    
608

  
567 609
    object_meta = []
568 610
    for meta in objects:
569 611
        if len(meta) == 1:
570 612
            # Virtual objects/directories.
571 613
            object_meta.append(meta)
572 614
        else:
573
            rename_meta_key(meta, 'hash', 'x_object_hash') # Will be replaced by checksum.
615
            rename_meta_key(
616
                meta, 'hash', 'x_object_hash')  # Will be replaced by checksum.
574 617
            rename_meta_key(meta, 'checksum', 'hash')
575 618
            rename_meta_key(meta, 'type', 'content_type')
576 619
            rename_meta_key(meta, 'uuid', 'x_object_uuid')
......
580 623
                rename_meta_key(meta, 'modified', 'last_modified')
581 624
            rename_meta_key(meta, 'modified_by', 'x_object_modified_by')
582 625
            rename_meta_key(meta, 'version', 'x_object_version')
583
            rename_meta_key(meta, 'version_timestamp', 'x_object_version_timestamp')
626
            rename_meta_key(
627
                meta, 'version_timestamp', 'x_object_version_timestamp')
584 628
            permissions = object_permissions.get(meta['name'], None)
585 629
            if permissions:
586
                update_sharing_meta(request, permissions, v_account, v_container, meta['name'], meta)
630
                update_sharing_meta(request, permissions, v_account,
631
                                    v_container, meta['name'], meta)
587 632
            public = object_public.get(meta['name'], None)
588 633
            if public:
589 634
                update_public_meta(public, meta)
590 635
            object_meta.append(printable_header_dict(meta))
591 636
    if request.serialization == 'xml':
592
        data = render_to_string('objects.xml', {'container': v_container, 'objects': object_meta})
593
    elif request.serialization  == 'json':
637
        data = render_to_string(
638
            'objects.xml', {'container': v_container, 'objects': object_meta})
639
    elif request.serialization == 'json':
594 640
        data = json.dumps(object_meta, default=json_encode_decimal)
595 641
    response.status_code = 200
596 642
    response.content = data
597 643
    return response
598 644

  
645

  
599 646
@api_method('HEAD')
600 647
def object_meta(request, v_account, v_container, v_object):
601 648
    # Normal Response Codes: 204
......
603 650
    #                       itemNotFound (404),
604 651
    #                       forbidden (403),
605 652
    #                       badRequest (400)
606
    
653

  
607 654
    version = request.GET.get('version')
608 655
    try:
609 656
        meta = request.backend.get_object_meta(request.user_uniq, v_account,
610
                                                v_container, v_object, 'pithos', version)
657
                                               v_container, v_object, 'pithos', version)
611 658
        if version is None:
612
            permissions = request.backend.get_object_permissions(request.user_uniq,
613
                                            v_account, v_container, v_object)
614
            public = request.backend.get_object_public(request.user_uniq, v_account,
615
                                                        v_container, v_object)
659
            permissions = request.backend.get_object_permissions(
660
                request.user_uniq,
661
                v_account, v_container, v_object)
662
            public = request.backend.get_object_public(
663
                request.user_uniq, v_account,
664
                v_container, v_object)
616 665
        else:
617 666
            permissions = None
618 667
            public = None
......
622 671
        raise ItemNotFound('Object does not exist')
623 672
    except VersionNotExists:
624 673
        raise ItemNotFound('Version does not exist')
625
    
674

  
626 675
    update_manifest_meta(request, v_account, meta)
627
    update_sharing_meta(request, permissions, v_account, v_container, v_object, meta)
676
    update_sharing_meta(
677
        request, permissions, v_account, v_container, v_object, meta)
628 678
    update_public_meta(public, meta)
629
    
679

  
630 680
    # Evaluate conditions.
631 681
    validate_modification_preconditions(request, meta)
632 682
    try:
......
635 685
        response = HttpResponse(status=304)
636 686
        response['ETag'] = meta['checksum']
637 687
        return response
638
    
688

  
639 689
    response = HttpResponse(status=200)
640 690
    put_object_headers(response, meta)
641 691
    return response
642 692

  
693

  
643 694
@api_method('GET', format_allowed=True)
644 695
def object_read(request, v_account, v_container, v_object):
645 696
    # Normal Response Codes: 200, 206
......
650 701
    #                       forbidden (403),
651 702
    #                       badRequest (400),
652 703
    #                       notModified (304)
653
    
704

  
654 705
    version = request.GET.get('version')
655
    
706

  
656 707
    # Reply with the version list. Do this first, as the object may be deleted.
657 708
    if version == 'list':
658 709
        if request.serialization == 'text':
659 710
            raise BadRequest('No format specified for version list.')
660
        
711

  
661 712
        try:
662 713
            v = request.backend.list_versions(request.user_uniq, v_account,
663
                                                v_container, v_object)
714
                                              v_container, v_object)
664 715
        except NotAllowedError:
665 716
            raise Forbidden('Not allowed')
666 717
        d = {'versions': v}
667 718
        if request.serialization == 'xml':
668 719
            d['object'] = v_object
669 720
            data = render_to_string('versions.xml', d)
670
        elif request.serialization  == 'json':
721
        elif request.serialization == 'json':
671 722
            data = json.dumps(d, default=json_encode_decimal)
672
        
723

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

  
677 728
    try:
678 729
        meta = request.backend.get_object_meta(request.user_uniq, v_account,
679
                                                v_container, v_object, 'pithos', version)
730
                                               v_container, v_object, 'pithos', version)
680 731
        if version is None:
681
            permissions = request.backend.get_object_permissions(request.user_uniq,
682
                                            v_account, v_container, v_object)
683
            public = request.backend.get_object_public(request.user_uniq, v_account,
684
                                                        v_container, v_object)
732
            permissions = request.backend.get_object_permissions(
733
                request.user_uniq,
734
                v_account, v_container, v_object)
735
            public = request.backend.get_object_public(
736
                request.user_uniq, v_account,
737
                v_container, v_object)
685 738
        else:
686 739
            permissions = None
687 740
            public = None
......
691 744
        raise ItemNotFound('Object does not exist')
692 745
    except VersionNotExists:
693 746
        raise ItemNotFound('Version does not exist')
694
    
747

  
695 748
    update_manifest_meta(request, v_account, meta)
696
    update_sharing_meta(request, permissions, v_account, v_container, v_object, meta)
749
    update_sharing_meta(
750
        request, permissions, v_account, v_container, v_object, meta)
697 751
    update_public_meta(public, meta)
698
    
752

  
699 753
    # Evaluate conditions.
700 754
    validate_modification_preconditions(request, meta)
701 755
    try:
......
704 758
        response = HttpResponse(status=304)
705 759
        response['ETag'] = meta['checksum']
706 760
        return response
707
    
761

  
708 762
    hashmap_reply = False
709 763
    if 'hashmap' in request.GET and request.serialization != 'text':
710 764
        hashmap_reply = True
711
    
765

  
712 766
    sizes = []
713 767
    hashmaps = []
714 768
    if 'X-Object-Manifest' in meta and not hashmap_reply:
715 769
        try:
716
            src_container, src_name = split_container_object_string('/' + meta['X-Object-Manifest'])
717
            objects = request.backend.list_objects(request.user_uniq, v_account,
718
                                src_container, prefix=src_name, virtual=False)
770
            src_container, src_name = split_container_object_string(
771
                '/' + meta['X-Object-Manifest'])
772
            objects = request.backend.list_objects(
773
                request.user_uniq, v_account,
774
                src_container, prefix=src_name, virtual=False)
719 775
        except NotAllowedError:
720 776
            raise Forbidden('Not allowed')
721 777
        except ValueError:
722 778
            raise BadRequest('Invalid X-Object-Manifest header')
723 779
        except ItemNotExists:
724 780
            raise ItemNotFound('Container does not exist')
725
        
781

  
726 782
        try:
727 783
            for x in objects:
728 784
                s, h = request.backend.get_object_hashmap(request.user_uniq,
729
                                        v_account, src_container, x[0], x[1])
785
                                                          v_account, src_container, x[0], x[1])
730 786
                sizes.append(s)
731 787
                hashmaps.append(h)
732 788
        except NotAllowedError:
......
737 793
            raise ItemNotFound('Version does not exist')
738 794
    else:
739 795
        try:
740
            s, h = request.backend.get_object_hashmap(request.user_uniq, v_account,
741
                                                v_container, v_object, version)
796
            s, h = request.backend.get_object_hashmap(
797
                request.user_uniq, v_account,
798
                v_container, v_object, version)
742 799
            sizes.append(s)
743 800
            hashmaps.append(h)
744 801
        except NotAllowedError:
......
747 804
            raise ItemNotFound('Object does not exist')
748 805
        except VersionNotExists:
749 806
            raise ItemNotFound('Version does not exist')
750
    
807

  
751 808
    # Reply with the hashmap.
752 809
    if hashmap_reply:
753 810
        size = sum(sizes)
......
760 817
        if request.serialization == 'xml':
761 818
            d['object'] = v_object
762 819
            data = render_to_string('hashes.xml', d)
763
        elif request.serialization  == 'json':
820
        elif request.serialization == 'json':
764 821
            data = json.dumps(d)
765
        
822

  
766 823
        response = HttpResponse(data, status=200)
767 824
        put_object_headers(response, meta)
768 825
        response['Content-Length'] = len(data)
769 826
        return response
770
    
771
    request.serialization = 'text' # Unset.
827

  
828
    request.serialization = 'text'  # Unset.
772 829
    return object_data_response(request, sizes, hashmaps, meta)
773 830

  
831

  
774 832
@api_method('PUT', format_allowed=True)
775 833
def object_write(request, v_account, v_container, v_object):
776 834
    # Normal Response Codes: 201
......
781 839
    #                       itemNotFound (404),
782 840
    #                       forbidden (403),
783 841
    #                       badRequest (400)
784
    
842

  
785 843
    # Evaluate conditions.
786 844
    if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
787 845
        try:
788
            meta = request.backend.get_object_meta(request.user_uniq, v_account,
789
                                                        v_container, v_object, 'pithos')
846
            meta = request.backend.get_object_meta(
847
                request.user_uniq, v_account,
848
                v_container, v_object, 'pithos')
790 849
        except NotAllowedError:
791 850
            raise Forbidden('Not allowed')
792 851
        except NameError:
793 852
            meta = {}
794 853
        validate_matching_preconditions(request, meta)
795
    
854

  
796 855
    copy_from = request.META.get('HTTP_X_COPY_FROM')
797 856
    move_from = request.META.get('HTTP_X_MOVE_FROM')
798 857
    if copy_from or move_from:
799 858
        delimiter = request.GET.get('delimiter')
800
        content_length = get_content_length(request) # Required by the API.
801
        
859
        content_length = get_content_length(request)  # Required by the API.
860

  
802 861
        src_account = request.META.get('HTTP_X_SOURCE_ACCOUNT')
803 862
        if not src_account:
804 863
            src_account = request.user_uniq
805 864
        if move_from:
806 865
            try:
807
                src_container, src_name = split_container_object_string(move_from)
866
                src_container, src_name = split_container_object_string(
867
                    move_from)
808 868
            except ValueError:
809 869
                raise BadRequest('Invalid X-Move-From header')
810
            version_id = copy_or_move_object(request, src_account, src_container, src_name,
811
                                                v_account, v_container, v_object, move=True, delimiter=delimiter)
870
            version_id = copy_or_move_object(
871
                request, src_account, src_container, src_name,
872
                v_account, v_container, v_object, move=True, delimiter=delimiter)
812 873
        else:
813 874
            try:
814
                src_container, src_name = split_container_object_string(copy_from)
875
                src_container, src_name = split_container_object_string(
876
                    copy_from)
815 877
            except ValueError:
816 878
                raise BadRequest('Invalid X-Copy-From header')
817
            version_id = copy_or_move_object(request, src_account, src_container, src_name,
818
                                                v_account, v_container, v_object, move=False, delimiter=delimiter)
879
            version_id = copy_or_move_object(
880
                request, src_account, src_container, src_name,
881
                v_account, v_container, v_object, move=False, delimiter=delimiter)
819 882
        response = HttpResponse(status=201)
820 883
        response['X-Object-Version'] = version_id
821 884
        return response
822
    
885

  
823 886
    content_type, meta, permissions, public = get_object_headers(request)
824 887
    content_length = -1
825 888
    if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
......
827 890
    # Should be BadRequest, but API says otherwise.
828 891
    if content_type is None:
829 892
        raise LengthRequired('Missing Content-Type header')
830
    
893

  
831 894
    if 'hashmap' in request.GET:
832 895
        if request.serialization not in ('json', 'xml'):
833 896
            raise BadRequest('Invalid hashmap format')
834
        
897

  
835 898
        data = ''
836 899
        for block in socket_read_iterator(request, content_length,
837
                                            request.backend.block_size):
900
                                          request.backend.block_size):
838 901
            data = '%s%s' % (data, block)
839
        
902

  
840 903
        if request.serialization == 'json':
841 904
            d = json.loads(data)
842 905
            if not hasattr(d, '__getitem__'):
......
851 914
                xml = minidom.parseString(data)
852 915
                obj = xml.getElementsByTagName('object')[0]
853 916
                size = int(obj.attributes['bytes'].value)
854
                
917

  
855 918
                hashes = xml.getElementsByTagName('hash')
856 919
                hashmap = []
857 920
                for hash in hashes:
858 921
                    hashmap.append(hash.firstChild.data)
859 922
            except:
860 923
                raise BadRequest('Invalid data formatting')
861
        
862
        checksum = '' # Do not set to None (will copy previous value).
924

  
925
        checksum = ''  # Do not set to None (will copy previous value).
863 926
    else:
864 927
        md5 = hashlib.md5()
865 928
        size = 0
866 929
        hashmap = []
867 930
        for data in socket_read_iterator(request, content_length,
868
                                            request.backend.block_size):
931
                                         request.backend.block_size):
869 932
            # TODO: Raise 408 (Request Timeout) if this takes too long.
870 933
            # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
871 934
            size += len(data)
872 935
            hashmap.append(request.backend.put_block(data))
873 936
            md5.update(data)
874
        
937

  
875 938
        checksum = md5.hexdigest().lower()
876 939
        etag = request.META.get('HTTP_ETAG')
877 940
        if etag and parse_etags(etag)[0].lower() != checksum:
878 941
            raise UnprocessableEntity('Object ETag does not match')
879
    
942

  
880 943
    try:
881 944
        version_id = request.backend.update_object_hashmap(request.user_uniq,
882
                        v_account, v_container, v_object, size, content_type,
883
                        hashmap, checksum, 'pithos', meta, True, permissions)
945
                                                           v_account, v_container, v_object, size, content_type,
946
                                                           hashmap, checksum, 'pithos', meta, True, permissions)
884 947
    except NotAllowedError:
885 948
        raise Forbidden('Not allowed')
886 949
    except IndexError, e:
......
896 959
        checksum = hashmap_md5(request.backend, hashmap, size)
897 960
        try:
898 961
            request.backend.update_object_checksum(request.user_uniq,
899
              v_account, v_container, v_object, version_id, checksum)
962
                                                   v_account, v_container, v_object, version_id, checksum)
900 963
        except NotAllowedError:
901 964
            raise Forbidden('Not allowed')
902 965
    if public is not None:
903 966
        try:
904 967
            request.backend.update_object_public(request.user_uniq, v_account,
905
                                                v_container, v_object, public)
968
                                                 v_container, v_object, public)
906 969
        except NotAllowedError:
907 970
            raise Forbidden('Not allowed')
908 971
        except ItemNotExists:
909 972
            raise ItemNotFound('Object does not exist')
910
    
973

  
911 974
    response = HttpResponse(status=201)
912 975
    if checksum:
913 976
        response['ETag'] = checksum
914 977
    response['X-Object-Version'] = version_id
915 978
    return response
916 979

  
980

  
917 981
@api_method('POST')
918 982
def object_write_form(request, v_account, v_container, v_object):
919 983
    # Normal Response Codes: 201
......
921 985
    #                       itemNotFound (404),
922 986
    #                       forbidden (403),
923 987
    #                       badRequest (400)
924
    
988

  
925 989
    request.upload_handlers = [SaveToBackendHandler(request)]
926
    if not request.FILES.has_key('X-Object-Data'):
990
    if 'X-Object-Data' not in request.FILES:
927 991
        raise BadRequest('Missing X-Object-Data field')
928 992
    file = request.FILES['X-Object-Data']
929
    
993

  
930 994
    checksum = file.etag
931 995
    try:
932 996
        version_id = request.backend.update_object_hashmap(request.user_uniq,
933
                        v_account, v_container, v_object, file.size, file.content_type,
934
                        file.hashmap, checksum, 'pithos', {}, True)
997
                                                           v_account, v_container, v_object, file.size, file.content_type,
998
                                                           file.hashmap, checksum, 'pithos', {}, True)
935 999
    except NotAllowedError:
936 1000
        raise Forbidden('Not allowed')
937 1001
    except ItemNotExists:
938 1002
        raise ItemNotFound('Container does not exist')
939 1003
    except QuotaError:
940 1004
        raise RequestEntityTooLarge('Quota exceeded')
941
    
1005

  
942 1006
    response = HttpResponse(status=201)
943 1007
    response['ETag'] = checksum
944 1008
    response['X-Object-Version'] = version_id
945 1009
    response.content = checksum
946 1010
    return response
947 1011

  
1012

  
948 1013
@api_method('COPY', format_allowed=True)
949 1014
def object_copy(request, v_account, v_container, v_object):
950 1015
    # Normal Response Codes: 201
......
952 1017
    #                       itemNotFound (404),
953 1018
    #                       forbidden (403),
954 1019
    #                       badRequest (400)
955
    
1020

  
956 1021
    dest_account = request.META.get('HTTP_DESTINATION_ACCOUNT')
957 1022
    if not dest_account:
958 1023
        dest_account = request.user_uniq
......
963 1028
        dest_container, dest_name = split_container_object_string(dest_path)
964 1029
    except ValueError:
965 1030
        raise BadRequest('Invalid Destination header')
966
    
1031

  
967 1032
    # Evaluate conditions.
968 1033
    if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
969 1034
        src_version = request.META.get('HTTP_X_SOURCE_VERSION')
970 1035
        try:
971
            meta = request.backend.get_object_meta(request.user_uniq, v_account,
972
                                            v_container, v_object, 'pithos', src_version)
1036
            meta = request.backend.get_object_meta(
1037
                request.user_uniq, v_account,
1038
                v_container, v_object, 'pithos', src_version)
973 1039
        except NotAllowedError:
974 1040
            raise Forbidden('Not allowed')
975 1041
        except (ItemNotExists, VersionNotExists):
976 1042
            raise ItemNotFound('Container or object does not exist')
977 1043
        validate_matching_preconditions(request, meta)
978
    
1044

  
979 1045
    delimiter = request.GET.get('delimiter')
980
    
1046

  
981 1047
    version_id = copy_or_move_object(request, v_account, v_container, v_object,
982
                                        dest_account, dest_container, dest_name, move=False, delimiter=delimiter)
1048
                                     dest_account, dest_container, dest_name, move=False, delimiter=delimiter)
983 1049
    response = HttpResponse(status=201)
984 1050
    response['X-Object-Version'] = version_id
985 1051
    return response
986 1052

  
1053

  
987 1054
@api_method('MOVE', format_allowed=True)
988 1055
def object_move(request, v_account, v_container, v_object):
989 1056
    # Normal Response Codes: 201
......
991 1058
    #                       itemNotFound (404),
992 1059
    #                       forbidden (403),
993 1060
    #                       badRequest (400)
994
    
1061

  
995 1062
    dest_account = request.META.get('HTTP_DESTINATION_ACCOUNT')
996 1063
    if not dest_account:
997 1064
        dest_account = request.user_uniq
......
1002 1069
        dest_container, dest_name = split_container_object_string(dest_path)
1003 1070
    except ValueError:
1004 1071
        raise BadRequest('Invalid Destination header')
1005
    
1072

  
1006 1073
    # Evaluate conditions.
1007 1074
    if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
1008 1075
        try:
1009
            meta = request.backend.get_object_meta(request.user_uniq, v_account,
1010
                                                    v_container, v_object, 'pithos')
1076
            meta = request.backend.get_object_meta(
1077
                request.user_uniq, v_account,
1078
                v_container, v_object, 'pithos')
1011 1079
        except NotAllowedError:
1012 1080
            raise Forbidden('Not allowed')
1013 1081
        except ItemNotExists:
1014 1082
            raise ItemNotFound('Container or object does not exist')
1015 1083
        validate_matching_preconditions(request, meta)
1016
    
1084

  
1017 1085
    delimiter = request.GET.get('delimiter')
1018
    
1086

  
1019 1087
    version_id = copy_or_move_object(request, v_account, v_container, v_object,
1020
                                        dest_account, dest_container, dest_name, move=True, delimiter=delimiter)
1088
                                     dest_account, dest_container, dest_name, move=True, delimiter=delimiter)
1021 1089
    response = HttpResponse(status=201)
1022 1090
    response['X-Object-Version'] = version_id
1023 1091
    return response
1024 1092

  
1093

  
1025 1094
@api_method('POST', format_allowed=True)
1026 1095
def object_update(request, v_account, v_container, v_object):
1027 1096
    # Normal Response Codes: 202, 204
......
1030 1099
    #                       itemNotFound (404),
1031 1100
    #                       forbidden (403),
1032 1101
    #                       badRequest (400)
1033
    
1102

  
1034 1103
    content_type, meta, permissions, public = get_object_headers(request)
1035
    
1104

  
1036 1105
    try:
1037
        prev_meta = request.backend.get_object_meta(request.user_uniq, v_account,
1038
                                                    v_container, v_object, 'pithos')
1106
        prev_meta = request.backend.get_object_meta(
1107
            request.user_uniq, v_account,
1108
            v_container, v_object, 'pithos')
1039 1109
    except NotAllowedError:
1040 1110
        raise Forbidden('Not allowed')
1041 1111
    except ItemNotExists:
1042 1112
        raise ItemNotFound('Object does not exist')
1043
    
1113

  
1044 1114
    # Evaluate conditions.
1045 1115
    if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
1046 1116
        validate_matching_preconditions(request, prev_meta)
1047
    
1117

  
1048 1118
    replace = True
1049 1119
    if 'update' in request.GET:
1050 1120
        replace = False
1051
    
1121

  
1052 1122
    # A Content-Type or X-Source-Object header indicates data updates.
1053 1123
    src_object = request.META.get('HTTP_X_SOURCE_OBJECT')
1054 1124
    if (not content_type or content_type != 'application/octet-stream') and not src_object:
1055 1125
        response = HttpResponse(status=202)
1056
        
1126

  
1057 1127
        # Do permissions first, as it may fail easier.
1058 1128
        if permissions is not None:
1059 1129
            try:
1060 1130
                request.backend.update_object_permissions(request.user_uniq,
1061
                                v_account, v_container, v_object, permissions)
1131
                                                          v_account, v_container, v_object, permissions)
1062 1132
            except NotAllowedError:
1063 1133
                raise Forbidden('Not allowed')
1064 1134
            except ItemNotExists:
......
1067 1137
                raise BadRequest('Invalid sharing header')
1068 1138
        if public is not None:
1069 1139
            try:
1070
                request.backend.update_object_public(request.user_uniq, v_account,
1071
                                                v_container, v_object, public)
1140
                request.backend.update_object_public(
1141
                    request.user_uniq, v_account,
1142
                    v_container, v_object, public)
1072 1143
            except NotAllowedError:
1073 1144
                raise Forbidden('Not allowed')
1074 1145
            except ItemNotExists:
1075 1146
                raise ItemNotFound('Object does not exist')
1076 1147
        if meta or replace:
1077 1148
            try:
1078
                version_id = request.backend.update_object_meta(request.user_uniq,
1079
                                v_account, v_container, v_object, 'pithos', meta, replace)
1149
                version_id = request.backend.update_object_meta(
1150
                    request.user_uniq,
1151
                    v_account, v_container, v_object, 'pithos', meta, replace)
1080 1152
            except NotAllowedError:
1081 1153
                raise Forbidden('Not allowed')
1082 1154
            except ItemNotExists:
1083
                raise ItemNotFound('Object does not exist')        
1155
                raise ItemNotFound('Object does not exist')
1084 1156
            response['X-Object-Version'] = version_id
1085
        
1157

  
1086 1158
        return response
1087
    
1159

  
1088 1160
    # Single range update. Range must be in Content-Range.
1089 1161
    # Based on: http://code.google.com/p/gears/wiki/ContentRangePostProposal
1090 1162
    # (with the addition that '*' is allowed for the range - will append).
......
1094 1166
    ranges = get_content_range(request)
1095 1167
    if not ranges:
1096 1168
        raise RangeNotSatisfiable('Invalid Content-Range header')
1097
    
1169

  
1098 1170
    try:
1099 1171
        size, hashmap = request.backend.get_object_hashmap(request.user_uniq,
1100
                                            v_account, v_container, v_object)
1172
                                                           v_account, v_container, v_object)
1101 1173
    except NotAllowedError:
1102 1174
        raise Forbidden('Not allowed')
1103 1175
    except ItemNotExists:
1104 1176
        raise ItemNotFound('Object does not exist')
1105
    
1177

  
1106 1178
    offset, length, total = ranges
1107 1179
    if offset is None:
1108 1180
        offset = size
......
1115 1187
        src_container, src_name = split_container_object_string(src_object)
1116 1188
        src_version = request.META.get('HTTP_X_SOURCE_VERSION')
1117 1189
        try:
1118
            src_size, src_hashmap = request.backend.get_object_hashmap(request.user_uniq,
1119
                                        src_account, src_container, src_name, src_version)
1190
            src_size, src_hashmap = request.backend.get_object_hashmap(
1191
                request.user_uniq,
1192
                src_account, src_container, src_name, src_version)
1120 1193
        except NotAllowedError:
1121 1194
            raise Forbidden('Not allowed')
1122 1195
        except ItemNotExists:
1123 1196
            raise ItemNotFound('Source object does not exist')
1124
        
1197

  
1125 1198
        if length is None:
1126 1199
            length = src_size
1127 1200
        elif length > src_size:
......
1131 1204
        content_length = -1
1132 1205
        if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
1133 1206
            content_length = get_content_length(request)
1134
        
1207

  
1135 1208
        if length is None:
1136 1209
            length = content_length
1137 1210
        else:
......
1141 1214
            elif length != content_length:
1142 1215
                raise BadRequest('Content length does not match range length')
1143 1216
    if total is not None and (total != size or offset >= size or (length > 0 and offset + length >= size)):
1144
        raise RangeNotSatisfiable('Supplied range will change provided object limits')
1145
    
1217
        raise RangeNotSatisfiable(
1218
            'Supplied range will change provided object limits')
1219

  
1146 1220
    dest_bytes = request.META.get('HTTP_X_OBJECT_BYTES')
1147 1221
    if dest_bytes is not None:
1148 1222
        dest_bytes = get_int_parameter(dest_bytes)
1149 1223
        if dest_bytes is None:
1150 1224
            raise BadRequest('Invalid X-Object-Bytes header')
1151
    
1225

  
1152 1226
    if src_object:
1153 1227
        if offset % request.backend.block_size == 0:
1154 1228
            # Update the hashes only.
......
1162 1236
                    else:
1163 1237
                        data = request.backend.get_block(src_hashmap[sbi])
1164 1238
                        hashmap[bi] = request.backend.update_block(hashmap[bi],
1165
                                                                data[:bl], 0)
1239
                                                                   data[:bl], 0)
1166 1240
                else:
1167 1241
                    hashmap.append(src_hashmap[sbi])
1168 1242
                offset += bl
......
1183 1257
    else:
1184 1258
        data = ''
1185 1259
        for d in socket_read_iterator(request, length,
1186
                                        request.backend.block_size):
1260
                                      request.backend.block_size):
1187 1261
            # TODO: Raise 408 (Request Timeout) if this takes too long.
1188 1262
            # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
1189 1263
            data += d
......
1192 1266
            data = data[bytes:]
1193 1267
        if len(data) > 0:
1194 1268
            put_object_block(request, hashmap, data, offset)
1195
    
1269

  
1196 1270
    if offset > size:
1197 1271
        size = offset
1198 1272
    if dest_bytes is not None and dest_bytes < size:
1199 1273
        size = dest_bytes
1200 1274
        hashmap = hashmap[:(int((size - 1) / request.backend.block_size) + 1)]
1201
    checksum = hashmap_md5(request.backend, hashmap, size) if UPDATE_MD5 else ''
1275
    checksum = hashmap_md5(
1276
        request.backend, hashmap, size) if UPDATE_MD5 else ''
1202 1277
    try:
1203 1278
        version_id = request.backend.update_object_hashmap(request.user_uniq,
1204
                        v_account, v_container, v_object, size, prev_meta['type'],
1205
                        hashmap, checksum, 'pithos', meta, replace, permissions)
1279
                                                           v_account, v_container, v_object, size, prev_meta[
1280
                                                           'type'],
1281
                                                           hashmap, checksum, 'pithos', meta, replace, permissions)
1206 1282
    except NotAllowedError:
1207 1283
        raise Forbidden('Not allowed')
1208 1284
    except ItemNotExists:
......
1214 1290
    if public is not None:
1215 1291
        try:
1216 1292
            request.backend.update_object_public(request.user_uniq, v_account,
1217
                                                v_container, v_object, public)
1293
                                                 v_container, v_object, public)
1218 1294
        except NotAllowedError:
1219 1295
            raise Forbidden('Not allowed')
1220 1296
        except ItemNotExists:
1221 1297
            raise ItemNotFound('Object does not exist')
1222
    
1298

  
1223 1299
    response = HttpResponse(status=204)
1224 1300
    response['ETag'] = checksum
1225 1301
    response['X-Object-Version'] = version_id
1226 1302
    return response
1227 1303

  
1304

  
1228 1305
@api_method('DELETE')
1229 1306
def object_delete(request, v_account, v_container, v_object):
1230 1307
    # Normal Response Codes: 204
......
1232 1309
    #                       itemNotFound (404),
1233 1310
    #                       forbidden (403),
1234 1311
    #                       badRequest (400)
1235
    
1312

  
1236 1313
    until = get_int_parameter(request.GET.get('until'))
1237 1314
    delimiter = request.GET.get('delimiter')
1238
    
1315

  
1239 1316
    try:
1240
        request.backend.delete_object(request.user_uniq, v_account, v_container,
1241
                                        v_object, until, delimiter=delimiter)
1317
        request.backend.delete_object(
1318
            request.user_uniq, v_account, v_container,
1319
            v_object, until, delimiter=delimiter)
1242 1320
    except NotAllowedError:
1243 1321
        raise Forbidden('Not allowed')
1244 1322
    except ItemNotExists:
1245 1323
        raise ItemNotFound('Object does not exist')
1246 1324
    return HttpResponse(status=204)
1247 1325

  
1326

  
1248 1327
@api_method()
1249 1328
def method_not_allowed(request):
1250 1329
    raise BadRequest('Method not allowed')

Also available in: Unified diff