from traceback import format_exc
from wsgiref.handlers import format_date_time
from binascii import hexlify, unhexlify
+from datetime import datetime, tzinfo, timedelta
from django.conf import settings
from django.http import HttpResponse
from django.utils.encoding import smart_str
from pithos.api.compat import parse_http_date_safe, parse_http_date
-from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, ItemNotFound,
- Conflict, LengthRequired, PreconditionFailed, RangeNotSatisfiable,
- ServiceUnavailable)
+from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound,
+ Conflict, LengthRequired, PreconditionFailed, RequestEntityTooLarge,
+ RangeNotSatisfiable, ServiceUnavailable)
from pithos.backends import connect_backend
-from pithos.backends.base import NotAllowedError
+from pithos.backends.base import NotAllowedError, QuotaError
-import datetime
import logging
import re
import hashlib
import uuid
-
+import decimal
logger = logging.getLogger(__name__)
+class UTC(tzinfo):
+ def utcoffset(self, dt):
+ return timedelta(0)
+
+ def tzname(self, dt):
+ return 'UTC'
+
+ def dst(self, dt):
+ return timedelta(0)
+
+def json_encode_decimal(obj):
+ if isinstance(obj, decimal.Decimal):
+ return str(obj)
+ raise TypeError(repr(obj) + " is not JSON serializable")
+
+def isoformat(d):
+ """Return an ISO8601 date string that includes a timezone."""
+
+ return d.replace(tzinfo=UTC()).isoformat()
+
def rename_meta_key(d, old, new):
if old not in d:
return
Format 'last_modified' timestamp.
"""
- d['last_modified'] = datetime.datetime.fromtimestamp(int(d['last_modified'])).isoformat()
+ d['last_modified'] = isoformat(datetime.fromtimestamp(d['last_modified']))
return dict([(k.lower().replace('-', '_'), v) for k, v in d.iteritems()])
def format_header_key(k):
if '-' in n or '_' in n:
raise BadRequest('Bad characters in group name')
groups[n] = v.replace(' ', '').split(',')
- if '' in groups[n]:
+ while '' in groups[n]:
groups[n].remove('')
return meta, groups
-def put_account_headers(response, meta, groups):
+def put_account_headers(response, meta, groups, policy):
if 'count' in meta:
response['X-Account-Container-Count'] = meta['count']
if 'bytes' in meta:
k = format_header_key('X-Account-Group-' + k)
v = smart_str(','.join(v), strings_only=True)
response[k] = v
-
+ for k, v in policy.iteritems():
+ response[smart_str(format_header_key('X-Account-Policy-' + k), strings_only=True)] = smart_str(v, strings_only=True)
+
def get_container_headers(request):
meta = get_header_prefix(request, 'X-Container-Meta-')
policy = dict([(k[19:].lower(), v.replace(' ', '')) for k, v in get_header_prefix(request, 'X-Container-Policy-').iteritems()])
raise ValueError
return s[:pos], s[(pos + 1):]
-def copy_or_move_object(request, v_account, src_container, src_name, dest_container, dest_name, move=False):
+def copy_or_move_object(request, src_account, src_container, src_name, dest_account, dest_container, dest_name, move=False):
"""Copy or move an object."""
meta, permissions, public = get_object_headers(request)
- src_version = request.META.get('HTTP_X_SOURCE_VERSION')
+ src_version = request.META.get('HTTP_X_SOURCE_VERSION')
try:
if move:
- version_id = request.backend.move_object(request.user, v_account,
- src_container, src_name, dest_container, dest_name,
- meta, False, permissions)
+ version_id = request.backend.move_object(request.user, src_account, src_container, src_name,
+ dest_account, dest_container, dest_name,
+ meta, False, permissions)
else:
- version_id = request.backend.copy_object(request.user, v_account,
- src_container, src_name, dest_container, dest_name,
- meta, False, permissions, src_version)
+ version_id = request.backend.copy_object(request.user, src_account, src_container, src_name,
+ dest_account, dest_container, dest_name,
+ meta, False, permissions, src_version)
except NotAllowedError:
- raise Unauthorized('Access denied')
+ raise Forbidden('Not allowed')
except (NameError, IndexError):
raise ItemNotFound('Container or object does not exist')
except ValueError:
raise BadRequest('Invalid sharing header')
except AttributeError, e:
- raise Conflict(json.dumps(e.data))
+ raise Conflict('\n'.join(e.data) + '\n')
+ except QuotaError:
+ raise RequestEntityTooLarge('Quota exceeded')
if public is not None:
try:
- request.backend.update_object_public(request.user, v_account,
- dest_container, dest_name, public)
+ request.backend.update_object_public(request.user, dest_account, dest_container, dest_name, public)
except NotAllowedError:
- raise Unauthorized('Access denied')
+ raise Forbidden('Not allowed')
except NameError:
raise ItemNotFound('Object does not exist')
return version_id
return request.environ['wsgi.input']
raise ServiceUnavailable('Unknown server software')
-MAX_UPLOAD_SIZE = 10 * (1024 * 1024) # 10MB
+MAX_UPLOAD_SIZE = 5 * (1024 * 1024 * 1024) # 5GB
def socket_read_iterator(request, length=0, blocksize=4096):
"""Return a maximum of blocksize data read from the socket in each iteration.
sock = raw_input_socket(request)
if length < 0: # Chunked transfers
# Small version (server does the dechunking).
- if request.environ.get('mod_wsgi.input_chunked', None):
+ if request.environ.get('mod_wsgi.input_chunked', None) or request.META['SERVER_SOFTWARE'].startswith('gunicorn'):
while length < MAX_UPLOAD_SIZE:
data = sock.read(blocksize)
if data == '':
return 'text'
-def api_method(http_method=None, format_allowed=False):
+def api_method(http_method=None, format_allowed=False, user_required=True):
"""Decorator function for views that implement an API method."""
def decorator(func):
try:
if http_method and request.method != http_method:
raise BadRequest('Method not allowed.')
+ if user_required and getattr(request, 'user', None) is None:
+ raise Unauthorized('Access denied')
# The args variable may contain up to (account, container, object).
if len(args) > 1 and len(args[1]) > 256:
fault = ServiceUnavailable('Unexpected error')
return render_fault(request, fault)
finally:
- request.backend.wrapper.conn.close()
+ if getattr(request, 'backend', None) is not None:
+ request.backend.wrapper.conn.close()
return wrapper
return decorator