Statistics
| Branch: | Tag: | Revision:

root / pithos / api / util.py @ 3436eeb0

History | View | Annotate | Download (25.3 kB)

1 5635f9ef Antony Chazapis
# Copyright 2011 GRNET S.A. All rights reserved.
2 5635f9ef Antony Chazapis
# 
3 5635f9ef Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 5635f9ef Antony Chazapis
# without modification, are permitted provided that the following
5 5635f9ef Antony Chazapis
# conditions are met:
6 5635f9ef Antony Chazapis
# 
7 5635f9ef Antony Chazapis
#   1. Redistributions of source code must retain the above
8 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
9 5635f9ef Antony Chazapis
#      disclaimer.
10 5635f9ef Antony Chazapis
# 
11 5635f9ef Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
13 5635f9ef Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 5635f9ef Antony Chazapis
#      provided with the distribution.
15 5635f9ef Antony Chazapis
# 
16 5635f9ef Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 5635f9ef Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 5635f9ef Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 5635f9ef Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 5635f9ef Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 5635f9ef Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 5635f9ef Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 5635f9ef Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 5635f9ef Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 5635f9ef Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 5635f9ef Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 5635f9ef Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 5635f9ef Antony Chazapis
# 
29 5635f9ef Antony Chazapis
# The views and conclusions contained in the software and
30 5635f9ef Antony Chazapis
# documentation are those of the authors and should not be
31 5635f9ef Antony Chazapis
# interpreted as representing official policies, either expressed
32 5635f9ef Antony Chazapis
# or implied, of GRNET S.A.
33 5635f9ef Antony Chazapis
34 b956618e Antony Chazapis
from functools import wraps
35 b956618e Antony Chazapis
from time import time
36 b956618e Antony Chazapis
from traceback import format_exc
37 b956618e Antony Chazapis
from wsgiref.handlers import format_date_time
38 49133392 Antony Chazapis
from binascii import hexlify
39 b956618e Antony Chazapis
40 b956618e Antony Chazapis
from django.conf import settings
41 b956618e Antony Chazapis
from django.http import HttpResponse
42 22dab079 Antony Chazapis
from django.utils.http import http_date, parse_etags
43 b956618e Antony Chazapis
44 b956618e Antony Chazapis
from pithos.api.compat import parse_http_date_safe
45 22dab079 Antony Chazapis
from pithos.api.faults import (Fault, NotModified, BadRequest, ItemNotFound, LengthRequired,
46 7bef5750 Antony Chazapis
                                PreconditionFailed, RangeNotSatisfiable, ServiceUnavailable)
47 b956618e Antony Chazapis
from pithos.backends import backend
48 b956618e Antony Chazapis
49 b956618e Antony Chazapis
import datetime
50 b956618e Antony Chazapis
import logging
51 22dab079 Antony Chazapis
import re
52 cbfb6636 Sofia Papagiannaki
import hashlib
53 7bef5750 Antony Chazapis
import uuid
54 b956618e Antony Chazapis
55 b956618e Antony Chazapis
56 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
57 b956618e Antony Chazapis
58 b956618e Antony Chazapis
59 b956618e Antony Chazapis
def printable_meta_dict(d):
60 b956618e Antony Chazapis
    """Format a meta dictionary for printing out json/xml.
61 b956618e Antony Chazapis
    
62 b956618e Antony Chazapis
    Convert all keys to lower case and replace dashes to underscores.
63 b956618e Antony Chazapis
    Change 'modified' key from backend to 'last_modified' and format date.
64 b956618e Antony Chazapis
    """
65 83dd59c5 Antony Chazapis
    
66 b956618e Antony Chazapis
    if 'modified' in d:
67 b956618e Antony Chazapis
        d['last_modified'] = datetime.datetime.fromtimestamp(int(d['modified'])).isoformat()
68 b956618e Antony Chazapis
        del(d['modified'])
69 b956618e Antony Chazapis
    return dict([(k.lower().replace('-', '_'), v) for k, v in d.iteritems()])
70 b956618e Antony Chazapis
71 b956618e Antony Chazapis
def format_meta_key(k):
72 58a6c894 Antony Chazapis
    """Convert underscores to dashes and capitalize intra-dash strings."""
73 83dd59c5 Antony Chazapis
    
74 b956618e Antony Chazapis
    return '-'.join([x.capitalize() for x in k.replace('_', '-').split('-')])
75 b956618e Antony Chazapis
76 b956618e Antony Chazapis
def get_meta_prefix(request, prefix):
77 58a6c894 Antony Chazapis
    """Get all prefix-* request headers in a dict. Reformat keys with format_meta_key()."""
78 83dd59c5 Antony Chazapis
    
79 b956618e Antony Chazapis
    prefix = 'HTTP_' + prefix.upper().replace('-', '_')
80 b956618e Antony Chazapis
    return dict([(format_meta_key(k[5:]), v) for k, v in request.META.iteritems() if k.startswith(prefix)])
81 b956618e Antony Chazapis
82 b956618e Antony Chazapis
def get_account_meta(request):
83 58a6c894 Antony Chazapis
    """Get metadata from an account request."""
84 83dd59c5 Antony Chazapis
    
85 b956618e Antony Chazapis
    meta = get_meta_prefix(request, 'X-Account-Meta-')    
86 b956618e Antony Chazapis
    return meta
87 b956618e Antony Chazapis
88 b956618e Antony Chazapis
def put_account_meta(response, meta):
89 58a6c894 Antony Chazapis
    """Put metadata in an account response."""
90 83dd59c5 Antony Chazapis
    
91 b956618e Antony Chazapis
    response['X-Account-Container-Count'] = meta['count']
92 b956618e Antony Chazapis
    response['X-Account-Bytes-Used'] = meta['bytes']
93 b956618e Antony Chazapis
    if 'modified' in meta:
94 b956618e Antony Chazapis
        response['Last-Modified'] = http_date(int(meta['modified']))
95 b956618e Antony Chazapis
    for k in [x for x in meta.keys() if x.startswith('X-Account-Meta-')]:
96 b956618e Antony Chazapis
        response[k.encode('utf-8')] = meta[k].encode('utf-8')
97 83dd59c5 Antony Chazapis
    if 'until_timestamp' in meta:
98 83dd59c5 Antony Chazapis
        response['X-Account-Until-Timestamp'] = http_date(int(meta['until_timestamp']))
99 b956618e Antony Chazapis
100 b956618e Antony Chazapis
def get_container_meta(request):
101 58a6c894 Antony Chazapis
    """Get metadata from a container request."""
102 83dd59c5 Antony Chazapis
    
103 b956618e Antony Chazapis
    meta = get_meta_prefix(request, 'X-Container-Meta-')
104 b956618e Antony Chazapis
    return meta
105 b956618e Antony Chazapis
106 b956618e Antony Chazapis
def put_container_meta(response, meta):
107 58a6c894 Antony Chazapis
    """Put metadata in a container response."""
108 83dd59c5 Antony Chazapis
    
109 b956618e Antony Chazapis
    response['X-Container-Object-Count'] = meta['count']
110 b956618e Antony Chazapis
    response['X-Container-Bytes-Used'] = meta['bytes']
111 58a6c894 Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
112 b956618e Antony Chazapis
    for k in [x for x in meta.keys() if x.startswith('X-Container-Meta-')]:
113 b956618e Antony Chazapis
        response[k.encode('utf-8')] = meta[k].encode('utf-8')
114 22dab079 Antony Chazapis
    response['X-Container-Object-Meta'] = [x[14:] for x in meta['object_meta'] if x.startswith('X-Object-Meta-')]
115 6bc372a4 Antony Chazapis
    response['X-Container-Block-Size'] = backend.block_size
116 6bc372a4 Antony Chazapis
    response['X-Container-Block-Hash'] = backend.hash_algorithm
117 83dd59c5 Antony Chazapis
    if 'until_timestamp' in meta:
118 83dd59c5 Antony Chazapis
        response['X-Container-Until-Timestamp'] = http_date(int(meta['until_timestamp']))
119 b956618e Antony Chazapis
120 b956618e Antony Chazapis
def get_object_meta(request):
121 58a6c894 Antony Chazapis
    """Get metadata from an object request."""
122 83dd59c5 Antony Chazapis
    
123 b956618e Antony Chazapis
    meta = get_meta_prefix(request, 'X-Object-Meta-')
124 b956618e Antony Chazapis
    if request.META.get('CONTENT_TYPE'):
125 b956618e Antony Chazapis
        meta['Content-Type'] = request.META['CONTENT_TYPE']
126 b956618e Antony Chazapis
    if request.META.get('HTTP_CONTENT_ENCODING'):
127 b956618e Antony Chazapis
        meta['Content-Encoding'] = request.META['HTTP_CONTENT_ENCODING']
128 22dab079 Antony Chazapis
    if request.META.get('HTTP_CONTENT_DISPOSITION'):
129 22dab079 Antony Chazapis
        meta['Content-Disposition'] = request.META['HTTP_CONTENT_DISPOSITION']
130 b956618e Antony Chazapis
    if request.META.get('HTTP_X_OBJECT_MANIFEST'):
131 b956618e Antony Chazapis
        meta['X-Object-Manifest'] = request.META['HTTP_X_OBJECT_MANIFEST']
132 b956618e Antony Chazapis
    return meta
133 b956618e Antony Chazapis
134 7bef5750 Antony Chazapis
def put_object_meta(response, meta, public=False):
135 58a6c894 Antony Chazapis
    """Put metadata in an object response."""
136 83dd59c5 Antony Chazapis
    
137 b956618e Antony Chazapis
    response['ETag'] = meta['hash']
138 b956618e Antony Chazapis
    response['Content-Length'] = meta['bytes']
139 b956618e Antony Chazapis
    response['Content-Type'] = meta.get('Content-Type', 'application/octet-stream')
140 b956618e Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
141 7bef5750 Antony Chazapis
    if not public:
142 7bef5750 Antony Chazapis
        response['X-Object-Version'] = meta['version']
143 7bef5750 Antony Chazapis
        response['X-Object-Version-Timestamp'] = meta['version_timestamp']
144 7bef5750 Antony Chazapis
        for k in [x for x in meta.keys() if x.startswith('X-Object-Meta-')]:
145 7bef5750 Antony Chazapis
            response[k.encode('utf-8')] = meta[k].encode('utf-8')
146 3436eeb0 Antony Chazapis
        for k in ('Content-Encoding', 'Content-Disposition', 'X-Object-Manifest', 'X-Object-Sharing'):
147 7bef5750 Antony Chazapis
            if k in meta:
148 7bef5750 Antony Chazapis
                response[k] = meta[k]
149 7bef5750 Antony Chazapis
    else:
150 c9af0703 Antony Chazapis
        for k in ('Content-Encoding', 'Content-Disposition'):
151 7bef5750 Antony Chazapis
            if k in meta:
152 7bef5750 Antony Chazapis
                response[k] = meta[k]
153 b956618e Antony Chazapis
154 8cb45c13 Antony Chazapis
def update_manifest_meta(request, v_account, meta):
155 8cb45c13 Antony Chazapis
    """Update metadata if the object has an X-Object-Manifest."""
156 8cb45c13 Antony Chazapis
    
157 8cb45c13 Antony Chazapis
    if 'X-Object-Manifest' in meta:
158 8cb45c13 Antony Chazapis
        hash = ''
159 8cb45c13 Antony Chazapis
        bytes = 0
160 8cb45c13 Antony Chazapis
        try:
161 6d817842 Antony Chazapis
            src_container, src_name = split_container_object_string('/' + meta['X-Object-Manifest'])
162 8cb45c13 Antony Chazapis
            objects = backend.list_objects(request.user, v_account, src_container, prefix=src_name, virtual=False)
163 8cb45c13 Antony Chazapis
            for x in objects:
164 8cb45c13 Antony Chazapis
                src_meta = backend.get_object_meta(request.user, v_account, src_container, x[0], x[1])
165 8cb45c13 Antony Chazapis
                hash += src_meta['hash']
166 8cb45c13 Antony Chazapis
                bytes += src_meta['bytes']
167 8cb45c13 Antony Chazapis
        except:
168 8cb45c13 Antony Chazapis
            # Ignore errors.
169 8cb45c13 Antony Chazapis
            return
170 8cb45c13 Antony Chazapis
        meta['bytes'] = bytes
171 8cb45c13 Antony Chazapis
        md5 = hashlib.md5()
172 8cb45c13 Antony Chazapis
        md5.update(hash)
173 8cb45c13 Antony Chazapis
        meta['hash'] = md5.hexdigest().lower()
174 8cb45c13 Antony Chazapis
175 3436eeb0 Antony Chazapis
def format_permissions(permissions):
176 3436eeb0 Antony Chazapis
    ret = []
177 3436eeb0 Antony Chazapis
    if 'public' in permissions:
178 3436eeb0 Antony Chazapis
        ret.append('public')
179 3436eeb0 Antony Chazapis
    if 'private' in permissions:
180 3436eeb0 Antony Chazapis
        ret.append('private')
181 3436eeb0 Antony Chazapis
    r = ','.join(permissions.get('read', []))
182 3436eeb0 Antony Chazapis
    if r:
183 3436eeb0 Antony Chazapis
        ret.append('read=' + r)
184 3436eeb0 Antony Chazapis
    w = ','.join(permissions.get('write', []))
185 3436eeb0 Antony Chazapis
    if w:
186 3436eeb0 Antony Chazapis
        ret.append('write=' + w)
187 3436eeb0 Antony Chazapis
    return '; '.join(ret)
188 3436eeb0 Antony Chazapis
189 b956618e Antony Chazapis
def validate_modification_preconditions(request, meta):
190 58a6c894 Antony Chazapis
    """Check that the modified timestamp conforms with the preconditions set."""
191 83dd59c5 Antony Chazapis
    
192 22dab079 Antony Chazapis
    if 'modified' not in meta:
193 22dab079 Antony Chazapis
        return # TODO: Always return?
194 22dab079 Antony Chazapis
    
195 b956618e Antony Chazapis
    if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
196 b956618e Antony Chazapis
    if if_modified_since is not None:
197 b956618e Antony Chazapis
        if_modified_since = parse_http_date_safe(if_modified_since)
198 22dab079 Antony Chazapis
    if if_modified_since is not None and int(meta['modified']) <= if_modified_since:
199 3e5d2f38 Antony Chazapis
        raise NotModified('Resource has not been modified')
200 b956618e Antony Chazapis
    
201 b956618e Antony Chazapis
    if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')
202 b956618e Antony Chazapis
    if if_unmodified_since is not None:
203 b956618e Antony Chazapis
        if_unmodified_since = parse_http_date_safe(if_unmodified_since)
204 22dab079 Antony Chazapis
    if if_unmodified_since is not None and int(meta['modified']) > if_unmodified_since:
205 3e5d2f38 Antony Chazapis
        raise PreconditionFailed('Resource has been modified')
206 b956618e Antony Chazapis
207 22dab079 Antony Chazapis
def validate_matching_preconditions(request, meta):
208 58a6c894 Antony Chazapis
    """Check that the ETag conforms with the preconditions set."""
209 83dd59c5 Antony Chazapis
    
210 22dab079 Antony Chazapis
    if 'hash' not in meta:
211 22dab079 Antony Chazapis
        return # TODO: Always return?
212 22dab079 Antony Chazapis
    
213 22dab079 Antony Chazapis
    if_match = request.META.get('HTTP_IF_MATCH')
214 22dab079 Antony Chazapis
    if if_match is not None and if_match != '*':
215 22dab079 Antony Chazapis
        if meta['hash'] not in [x.lower() for x in parse_etags(if_match)]:
216 3e5d2f38 Antony Chazapis
            raise PreconditionFailed('Resource Etag does not match')
217 22dab079 Antony Chazapis
    
218 22dab079 Antony Chazapis
    if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
219 22dab079 Antony Chazapis
    if if_none_match is not None:
220 22dab079 Antony Chazapis
        if if_none_match == '*' or meta['hash'] in [x.lower() for x in parse_etags(if_none_match)]:
221 3e5d2f38 Antony Chazapis
            raise NotModified('Resource Etag matches')
222 22dab079 Antony Chazapis
223 83dd59c5 Antony Chazapis
def split_container_object_string(s):
224 6d817842 Antony Chazapis
    if not len(s) > 0 or s[0] != '/':
225 6d817842 Antony Chazapis
        raise ValueError
226 6d817842 Antony Chazapis
    s = s[1:]
227 8cb45c13 Antony Chazapis
    pos = s.find('/')
228 8cb45c13 Antony Chazapis
    if pos == -1:
229 83dd59c5 Antony Chazapis
        raise ValueError
230 8cb45c13 Antony Chazapis
    return s[:pos], s[(pos + 1):]
231 83dd59c5 Antony Chazapis
232 83dd59c5 Antony Chazapis
def copy_or_move_object(request, v_account, src_container, src_name, dest_container, dest_name, move=False):
233 58a6c894 Antony Chazapis
    """Copy or move an object."""
234 22dab079 Antony Chazapis
    
235 b956618e Antony Chazapis
    meta = get_object_meta(request)
236 3436eeb0 Antony Chazapis
    permissions = get_sharing(request)
237 22dab079 Antony Chazapis
    # Keep previous values of 'Content-Type' (if a new one is absent) and 'hash'.
238 22dab079 Antony Chazapis
    try:
239 83dd59c5 Antony Chazapis
        src_meta = backend.get_object_meta(request.user, v_account, src_container, src_name)
240 22dab079 Antony Chazapis
    except NameError:
241 22dab079 Antony Chazapis
        raise ItemNotFound('Container or object does not exist')
242 22dab079 Antony Chazapis
    if 'Content-Type' in meta and 'Content-Type' in src_meta:
243 22dab079 Antony Chazapis
        del(src_meta['Content-Type'])
244 22dab079 Antony Chazapis
    for k in ('Content-Type', 'hash'):
245 22dab079 Antony Chazapis
        if k in src_meta:
246 22dab079 Antony Chazapis
            meta[k] = src_meta[k]
247 22dab079 Antony Chazapis
    
248 b956618e Antony Chazapis
    try:
249 b956618e Antony Chazapis
        if move:
250 3436eeb0 Antony Chazapis
            backend.move_object(request.user, v_account, src_container, src_name, dest_container, dest_name, meta, True, permissions)
251 b956618e Antony Chazapis
        else:
252 cb146cf9 Antony Chazapis
            src_version = request.META.get('HTTP_X_SOURCE_VERSION')
253 3436eeb0 Antony Chazapis
            backend.copy_object(request.user, v_account, src_container, src_name, dest_container, dest_name, meta, True, permissions, src_version)
254 b956618e Antony Chazapis
    except NameError:
255 b956618e Antony Chazapis
        raise ItemNotFound('Container or object does not exist')
256 3436eeb0 Antony Chazapis
    except ValueError:
257 3436eeb0 Antony Chazapis
        raise BadRequest('Invalid sharing header')
258 3436eeb0 Antony Chazapis
    except AttributeError:
259 3436eeb0 Antony Chazapis
        raise Conflict('Sharing already set above or below this path in the hierarchy')
260 b956618e Antony Chazapis
261 83dd59c5 Antony Chazapis
def get_int_parameter(request, name):
262 83dd59c5 Antony Chazapis
    p = request.GET.get(name)
263 83dd59c5 Antony Chazapis
    if p is not None:
264 58a6c894 Antony Chazapis
        try:
265 83dd59c5 Antony Chazapis
            p = int(p)
266 58a6c894 Antony Chazapis
        except ValueError:
267 58a6c894 Antony Chazapis
            return None
268 83dd59c5 Antony Chazapis
        if p < 0:
269 58a6c894 Antony Chazapis
            return None
270 83dd59c5 Antony Chazapis
    return p
271 58a6c894 Antony Chazapis
272 22dab079 Antony Chazapis
def get_content_length(request):
273 22dab079 Antony Chazapis
    content_length = request.META.get('CONTENT_LENGTH')
274 22dab079 Antony Chazapis
    if not content_length:
275 22dab079 Antony Chazapis
        raise LengthRequired('Missing Content-Length header')
276 22dab079 Antony Chazapis
    try:
277 22dab079 Antony Chazapis
        content_length = int(content_length)
278 22dab079 Antony Chazapis
        if content_length < 0:
279 22dab079 Antony Chazapis
            raise ValueError
280 22dab079 Antony Chazapis
    except ValueError:
281 22dab079 Antony Chazapis
        raise BadRequest('Invalid Content-Length header')
282 22dab079 Antony Chazapis
    return content_length
283 22dab079 Antony Chazapis
284 22dab079 Antony Chazapis
def get_range(request, size):
285 58a6c894 Antony Chazapis
    """Parse a Range header from the request.
286 b956618e Antony Chazapis
    
287 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
288 22dab079 Antony Chazapis
    or a list of (offset, length) tuples - should be further checked.
289 b956618e Antony Chazapis
    """
290 83dd59c5 Antony Chazapis
    
291 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_RANGE', '').replace(' ', '')
292 22dab079 Antony Chazapis
    if not ranges.startswith('bytes='):
293 b956618e Antony Chazapis
        return None
294 b956618e Antony Chazapis
    
295 22dab079 Antony Chazapis
    ret = []
296 22dab079 Antony Chazapis
    for r in (x.strip() for x in ranges[6:].split(',')):
297 22dab079 Antony Chazapis
        p = re.compile('^(?P<offset>\d*)-(?P<upto>\d*)$')
298 22dab079 Antony Chazapis
        m = p.match(r)
299 22dab079 Antony Chazapis
        if not m:
300 22dab079 Antony Chazapis
            return None
301 22dab079 Antony Chazapis
        offset = m.group('offset')
302 22dab079 Antony Chazapis
        upto = m.group('upto')
303 22dab079 Antony Chazapis
        if offset == '' and upto == '':
304 b956618e Antony Chazapis
            return None
305 b956618e Antony Chazapis
        
306 22dab079 Antony Chazapis
        if offset != '':
307 22dab079 Antony Chazapis
            offset = int(offset)
308 22dab079 Antony Chazapis
            if upto != '':
309 b956618e Antony Chazapis
                upto = int(upto)
310 22dab079 Antony Chazapis
                if offset > upto:
311 22dab079 Antony Chazapis
                    return None
312 22dab079 Antony Chazapis
                ret.append((offset, upto - offset + 1))
313 22dab079 Antony Chazapis
            else:
314 22dab079 Antony Chazapis
                ret.append((offset, size - offset))
315 b956618e Antony Chazapis
        else:
316 22dab079 Antony Chazapis
            length = int(upto)
317 22dab079 Antony Chazapis
            ret.append((size - length, length))
318 22dab079 Antony Chazapis
    
319 22dab079 Antony Chazapis
    return ret
320 22dab079 Antony Chazapis
321 22dab079 Antony Chazapis
def get_content_range(request):
322 58a6c894 Antony Chazapis
    """Parse a Content-Range header from the request.
323 22dab079 Antony Chazapis
    
324 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
325 22dab079 Antony Chazapis
    or an (offset, length, total) tuple - check as length, total may be None.
326 22dab079 Antony Chazapis
    Returns (None, None, None) if the provided range is '*/*'.
327 22dab079 Antony Chazapis
    """
328 22dab079 Antony Chazapis
    
329 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_CONTENT_RANGE', '')
330 22dab079 Antony Chazapis
    if not ranges:
331 22dab079 Antony Chazapis
        return None
332 22dab079 Antony Chazapis
    
333 22dab079 Antony Chazapis
    p = re.compile('^bytes (?P<offset>\d+)-(?P<upto>\d*)/(?P<total>(\d+|\*))$')
334 22dab079 Antony Chazapis
    m = p.match(ranges)
335 22dab079 Antony Chazapis
    if not m:
336 22dab079 Antony Chazapis
        if ranges == 'bytes */*':
337 22dab079 Antony Chazapis
            return (None, None, None)
338 22dab079 Antony Chazapis
        return None
339 22dab079 Antony Chazapis
    offset = int(m.group('offset'))
340 22dab079 Antony Chazapis
    upto = m.group('upto')
341 22dab079 Antony Chazapis
    total = m.group('total')
342 22dab079 Antony Chazapis
    if upto != '':
343 22dab079 Antony Chazapis
        upto = int(upto)
344 b956618e Antony Chazapis
    else:
345 22dab079 Antony Chazapis
        upto = None
346 22dab079 Antony Chazapis
    if total != '*':
347 22dab079 Antony Chazapis
        total = int(total)
348 22dab079 Antony Chazapis
    else:
349 22dab079 Antony Chazapis
        total = None
350 70e526a0 Antony Chazapis
    if (upto is not None and offset > upto) or \
351 70e526a0 Antony Chazapis
        (total is not None and offset >= total) or \
352 70e526a0 Antony Chazapis
        (total is not None and upto is not None and upto >= total):
353 22dab079 Antony Chazapis
        return None
354 22dab079 Antony Chazapis
    
355 70e526a0 Antony Chazapis
    if upto is None:
356 22dab079 Antony Chazapis
        length = None
357 22dab079 Antony Chazapis
    else:
358 22dab079 Antony Chazapis
        length = upto - offset + 1
359 22dab079 Antony Chazapis
    return (offset, length, total)
360 b956618e Antony Chazapis
361 3436eeb0 Antony Chazapis
def get_sharing(request):
362 3436eeb0 Antony Chazapis
    """Parse an X-Object-Sharing header from the request.
363 3436eeb0 Antony Chazapis
    
364 3436eeb0 Antony Chazapis
    Raises BadRequest on error.
365 3436eeb0 Antony Chazapis
    """
366 3436eeb0 Antony Chazapis
    
367 3436eeb0 Antony Chazapis
    permissions = request.META.get('HTTP_X_OBJECT_SHARING')
368 3436eeb0 Antony Chazapis
    if permissions is None or permissions == '':
369 3436eeb0 Antony Chazapis
        return None
370 3436eeb0 Antony Chazapis
    
371 3436eeb0 Antony Chazapis
    ret = {}
372 3436eeb0 Antony Chazapis
    for perm in (x.replace(' ','') for x in permissions.split(';')):
373 3436eeb0 Antony Chazapis
        if perm == 'public':
374 3436eeb0 Antony Chazapis
            ret['public'] = True
375 3436eeb0 Antony Chazapis
            continue
376 3436eeb0 Antony Chazapis
        elif perm == 'private':
377 3436eeb0 Antony Chazapis
            ret['private'] = True
378 3436eeb0 Antony Chazapis
            continue
379 3436eeb0 Antony Chazapis
        elif perm.startswith('read='):
380 3436eeb0 Antony Chazapis
            ret['read'] = [v.replace(' ','') for v in perm[5:].split(',')]
381 3436eeb0 Antony Chazapis
            if len(ret['read']) == 0:
382 3436eeb0 Antony Chazapis
                raise BadRequest('Bad X-Object-Sharing header value')
383 3436eeb0 Antony Chazapis
        elif perm.startswith('write='):
384 3436eeb0 Antony Chazapis
            ret['write'] = [v.replace(' ','') for v in perm[6:].split(',')]
385 3436eeb0 Antony Chazapis
            if len(ret['write']) == 0:
386 3436eeb0 Antony Chazapis
                raise BadRequest('Bad X-Object-Sharing header value')
387 3436eeb0 Antony Chazapis
        else:
388 3436eeb0 Antony Chazapis
            raise BadRequest('Bad X-Object-Sharing header value')
389 3436eeb0 Antony Chazapis
    return ret
390 3436eeb0 Antony Chazapis
391 b956618e Antony Chazapis
def raw_input_socket(request):
392 58a6c894 Antony Chazapis
    """Return the socket for reading the rest of the request."""
393 83dd59c5 Antony Chazapis
    
394 b956618e Antony Chazapis
    server_software = request.META.get('SERVER_SOFTWARE')
395 b956618e Antony Chazapis
    if not server_software:
396 b956618e Antony Chazapis
        if 'wsgi.input' in request.environ:
397 b956618e Antony Chazapis
            return request.environ['wsgi.input']
398 b956618e Antony Chazapis
        raise ServiceUnavailable('Unknown server software')
399 b956618e Antony Chazapis
    if server_software.startswith('WSGIServer'):
400 b956618e Antony Chazapis
        return request.environ['wsgi.input']
401 b956618e Antony Chazapis
    elif server_software.startswith('mod_python'):
402 b956618e Antony Chazapis
        return request._req
403 b956618e Antony Chazapis
    raise ServiceUnavailable('Unknown server software')
404 b956618e Antony Chazapis
405 b956618e Antony Chazapis
MAX_UPLOAD_SIZE = 10 * (1024 * 1024) # 10MB
406 b956618e Antony Chazapis
407 22dab079 Antony Chazapis
def socket_read_iterator(sock, length=0, blocksize=4096):
408 58a6c894 Antony Chazapis
    """Return a maximum of blocksize data read from the socket in each iteration.
409 b956618e Antony Chazapis
    
410 22dab079 Antony Chazapis
    Read up to 'length'. If 'length' is negative, will attempt a chunked read.
411 b956618e Antony Chazapis
    The maximum ammount of data read is controlled by MAX_UPLOAD_SIZE.
412 b956618e Antony Chazapis
    """
413 83dd59c5 Antony Chazapis
    
414 b956618e Antony Chazapis
    if length < 0: # Chunked transfers
415 22dab079 Antony Chazapis
        data = ''
416 b956618e Antony Chazapis
        while length < MAX_UPLOAD_SIZE:
417 22dab079 Antony Chazapis
            # Get chunk size.
418 22dab079 Antony Chazapis
            if hasattr(sock, 'readline'):
419 22dab079 Antony Chazapis
                chunk_length = sock.readline()
420 22dab079 Antony Chazapis
            else:
421 22dab079 Antony Chazapis
                chunk_length = ''
422 22dab079 Antony Chazapis
                while chunk_length[-1:] != '\n':
423 22dab079 Antony Chazapis
                    chunk_length += sock.read(1)
424 22dab079 Antony Chazapis
                chunk_length.strip()
425 b956618e Antony Chazapis
            pos = chunk_length.find(';')
426 b956618e Antony Chazapis
            if pos >= 0:
427 b956618e Antony Chazapis
                chunk_length = chunk_length[:pos]
428 b956618e Antony Chazapis
            try:
429 b956618e Antony Chazapis
                chunk_length = int(chunk_length, 16)
430 b956618e Antony Chazapis
            except Exception, e:
431 b956618e Antony Chazapis
                raise BadRequest('Bad chunk size') # TODO: Change to something more appropriate.
432 22dab079 Antony Chazapis
            # Check if done.
433 b956618e Antony Chazapis
            if chunk_length == 0:
434 22dab079 Antony Chazapis
                if len(data) > 0:
435 22dab079 Antony Chazapis
                    yield data
436 b956618e Antony Chazapis
                return
437 22dab079 Antony Chazapis
            # Get the actual data.
438 b956618e Antony Chazapis
            while chunk_length > 0:
439 22dab079 Antony Chazapis
                chunk = sock.read(min(chunk_length, blocksize))
440 22dab079 Antony Chazapis
                chunk_length -= len(chunk)
441 623a0cf4 Sofia Papagiannaki
                if length > 0:
442 623a0cf4 Sofia Papagiannaki
                    length += len(chunk)
443 22dab079 Antony Chazapis
                data += chunk
444 22dab079 Antony Chazapis
                if len(data) >= blocksize:
445 22dab079 Antony Chazapis
                    ret = data[:blocksize]
446 22dab079 Antony Chazapis
                    data = data[blocksize:]
447 22dab079 Antony Chazapis
                    yield ret
448 22dab079 Antony Chazapis
            sock.read(2) # CRLF
449 b956618e Antony Chazapis
        # TODO: Raise something to note that maximum size is reached.
450 b956618e Antony Chazapis
    else:
451 b956618e Antony Chazapis
        if length > MAX_UPLOAD_SIZE:
452 b956618e Antony Chazapis
            # TODO: Raise something to note that maximum size is reached.
453 b956618e Antony Chazapis
            pass
454 b956618e Antony Chazapis
        while length > 0:
455 b956618e Antony Chazapis
            data = sock.read(min(length, blocksize))
456 b956618e Antony Chazapis
            length -= len(data)
457 b956618e Antony Chazapis
            yield data
458 b956618e Antony Chazapis
459 22dab079 Antony Chazapis
class ObjectWrapper(object):
460 58a6c894 Antony Chazapis
    """Return the object's data block-per-block in each iteration.
461 22dab079 Antony Chazapis
    
462 22dab079 Antony Chazapis
    Read from the object using the offset and length provided in each entry of the range list.
463 22dab079 Antony Chazapis
    """
464 22dab079 Antony Chazapis
    
465 8cb45c13 Antony Chazapis
    def __init__(self, ranges, sizes, hashmaps, boundary):
466 22dab079 Antony Chazapis
        self.ranges = ranges
467 8cb45c13 Antony Chazapis
        self.sizes = sizes
468 8cb45c13 Antony Chazapis
        self.hashmaps = hashmaps
469 22dab079 Antony Chazapis
        self.boundary = boundary
470 8cb45c13 Antony Chazapis
        self.size = sum(self.sizes)
471 22dab079 Antony Chazapis
        
472 8cb45c13 Antony Chazapis
        self.file_index = 0
473 8cb45c13 Antony Chazapis
        self.block_index = 0
474 8cb45c13 Antony Chazapis
        self.block_hash = -1
475 22dab079 Antony Chazapis
        self.block = ''
476 22dab079 Antony Chazapis
        
477 22dab079 Antony Chazapis
        self.range_index = -1
478 22dab079 Antony Chazapis
        self.offset, self.length = self.ranges[0]
479 22dab079 Antony Chazapis
    
480 22dab079 Antony Chazapis
    def __iter__(self):
481 22dab079 Antony Chazapis
        return self
482 22dab079 Antony Chazapis
    
483 22dab079 Antony Chazapis
    def part_iterator(self):
484 22dab079 Antony Chazapis
        if self.length > 0:
485 8cb45c13 Antony Chazapis
            # Get the file for the current offset.
486 8cb45c13 Antony Chazapis
            file_size = self.sizes[self.file_index]
487 8cb45c13 Antony Chazapis
            while self.offset >= file_size:
488 8cb45c13 Antony Chazapis
                self.offset -= file_size
489 8cb45c13 Antony Chazapis
                self.file_index += 1
490 8cb45c13 Antony Chazapis
                file_size = self.sizes[self.file_index]
491 8cb45c13 Antony Chazapis
            
492 8cb45c13 Antony Chazapis
            # Get the block for the current position.
493 8cb45c13 Antony Chazapis
            self.block_index = int(self.offset / backend.block_size)
494 8cb45c13 Antony Chazapis
            if self.block_hash != self.hashmaps[self.file_index][self.block_index]:
495 8cb45c13 Antony Chazapis
                self.block_hash = self.hashmaps[self.file_index][self.block_index]
496 22dab079 Antony Chazapis
                try:
497 8cb45c13 Antony Chazapis
                    self.block = backend.get_block(self.block_hash)
498 22dab079 Antony Chazapis
                except NameError:
499 22dab079 Antony Chazapis
                    raise ItemNotFound('Block does not exist')
500 8cb45c13 Antony Chazapis
            
501 22dab079 Antony Chazapis
            # Get the data from the block.
502 22dab079 Antony Chazapis
            bo = self.offset % backend.block_size
503 8cb45c13 Antony Chazapis
            bl = min(self.length, len(self.block) - bo)
504 22dab079 Antony Chazapis
            data = self.block[bo:bo + bl]
505 22dab079 Antony Chazapis
            self.offset += bl
506 22dab079 Antony Chazapis
            self.length -= bl
507 22dab079 Antony Chazapis
            return data
508 22dab079 Antony Chazapis
        else:
509 22dab079 Antony Chazapis
            raise StopIteration
510 22dab079 Antony Chazapis
    
511 22dab079 Antony Chazapis
    def next(self):
512 22dab079 Antony Chazapis
        if len(self.ranges) == 1:
513 22dab079 Antony Chazapis
            return self.part_iterator()
514 22dab079 Antony Chazapis
        if self.range_index == len(self.ranges):
515 22dab079 Antony Chazapis
            raise StopIteration
516 22dab079 Antony Chazapis
        try:
517 22dab079 Antony Chazapis
            if self.range_index == -1:
518 22dab079 Antony Chazapis
                raise StopIteration
519 22dab079 Antony Chazapis
            return self.part_iterator()
520 22dab079 Antony Chazapis
        except StopIteration:
521 22dab079 Antony Chazapis
            self.range_index += 1
522 22dab079 Antony Chazapis
            out = []
523 22dab079 Antony Chazapis
            if self.range_index < len(self.ranges):
524 22dab079 Antony Chazapis
                # Part header.
525 22dab079 Antony Chazapis
                self.offset, self.length = self.ranges[self.range_index]
526 8cb45c13 Antony Chazapis
                self.file_index = 0
527 22dab079 Antony Chazapis
                if self.range_index > 0:
528 22dab079 Antony Chazapis
                    out.append('')
529 22dab079 Antony Chazapis
                out.append('--' + self.boundary)
530 22dab079 Antony Chazapis
                out.append('Content-Range: bytes %d-%d/%d' % (self.offset, self.offset + self.length - 1, self.size))
531 22dab079 Antony Chazapis
                out.append('Content-Transfer-Encoding: binary')
532 22dab079 Antony Chazapis
                out.append('')
533 22dab079 Antony Chazapis
                out.append('')
534 22dab079 Antony Chazapis
                return '\r\n'.join(out)
535 22dab079 Antony Chazapis
            else:
536 22dab079 Antony Chazapis
                # Footer.
537 22dab079 Antony Chazapis
                out.append('')
538 22dab079 Antony Chazapis
                out.append('--' + self.boundary + '--')
539 22dab079 Antony Chazapis
                out.append('')
540 22dab079 Antony Chazapis
                return '\r\n'.join(out)
541 22dab079 Antony Chazapis
542 8cb45c13 Antony Chazapis
def object_data_response(request, sizes, hashmaps, meta, public=False):
543 7bef5750 Antony Chazapis
    """Get the HttpResponse object for replying with the object's data."""
544 7bef5750 Antony Chazapis
    
545 7bef5750 Antony Chazapis
    # Range handling.
546 8cb45c13 Antony Chazapis
    size = sum(sizes)
547 7bef5750 Antony Chazapis
    ranges = get_range(request, size)
548 7bef5750 Antony Chazapis
    if ranges is None:
549 7bef5750 Antony Chazapis
        ranges = [(0, size)]
550 7bef5750 Antony Chazapis
        ret = 200
551 7bef5750 Antony Chazapis
    else:
552 7bef5750 Antony Chazapis
        check = [True for offset, length in ranges if
553 7bef5750 Antony Chazapis
                    length <= 0 or length > size or
554 7bef5750 Antony Chazapis
                    offset < 0 or offset >= size or
555 7bef5750 Antony Chazapis
                    offset + length > size]
556 7bef5750 Antony Chazapis
        if len(check) > 0:
557 7bef5750 Antony Chazapis
            raise RangeNotSatisfiable('Requested range exceeds object limits')        
558 7bef5750 Antony Chazapis
        ret = 206
559 7bef5750 Antony Chazapis
    
560 7bef5750 Antony Chazapis
    if ret == 206 and len(ranges) > 1:
561 7bef5750 Antony Chazapis
        boundary = uuid.uuid4().hex
562 7bef5750 Antony Chazapis
    else:
563 7bef5750 Antony Chazapis
        boundary = ''
564 8cb45c13 Antony Chazapis
    wrapper = ObjectWrapper(ranges, sizes, hashmaps, boundary)
565 7bef5750 Antony Chazapis
    response = HttpResponse(wrapper, status=ret)
566 7bef5750 Antony Chazapis
    put_object_meta(response, meta, public)
567 7bef5750 Antony Chazapis
    if ret == 206:
568 7bef5750 Antony Chazapis
        if len(ranges) == 1:
569 7bef5750 Antony Chazapis
            offset, length = ranges[0]
570 7bef5750 Antony Chazapis
            response['Content-Length'] = length # Update with the correct length.
571 7bef5750 Antony Chazapis
            response['Content-Range'] = 'bytes %d-%d/%d' % (offset, offset + length - 1, size)
572 7bef5750 Antony Chazapis
        else:
573 7bef5750 Antony Chazapis
            del(response['Content-Length'])
574 7bef5750 Antony Chazapis
            response['Content-Type'] = 'multipart/byteranges; boundary=%s' % (boundary,)
575 7bef5750 Antony Chazapis
    return response
576 7bef5750 Antony Chazapis
577 cb146cf9 Antony Chazapis
def put_object_block(hashmap, data, offset):
578 cb146cf9 Antony Chazapis
    """Put one block of data at the given offset."""
579 cb146cf9 Antony Chazapis
    
580 cb146cf9 Antony Chazapis
    bi = int(offset / backend.block_size)
581 cb146cf9 Antony Chazapis
    bo = offset % backend.block_size
582 cb146cf9 Antony Chazapis
    bl = min(len(data), backend.block_size - bo)
583 cb146cf9 Antony Chazapis
    if bi < len(hashmap):
584 cb146cf9 Antony Chazapis
        hashmap[bi] = backend.update_block(hashmap[bi], data[:bl], bo)
585 cb146cf9 Antony Chazapis
    else:
586 cb146cf9 Antony Chazapis
        hashmap.append(backend.put_block(('\x00' * bo) + data[:bl]))
587 cb146cf9 Antony Chazapis
    return bl # Return ammount of data written.
588 cb146cf9 Antony Chazapis
589 49133392 Antony Chazapis
def hashmap_hash(hashmap):
590 58a6c894 Antony Chazapis
    """Produce the root hash, treating the hashmap as a Merkle-like tree."""
591 49133392 Antony Chazapis
    
592 49133392 Antony Chazapis
    def subhash(d):
593 49133392 Antony Chazapis
        h = hashlib.new(backend.hash_algorithm)
594 49133392 Antony Chazapis
        h.update(d)
595 49133392 Antony Chazapis
        return h.digest()
596 49133392 Antony Chazapis
    
597 6ecccd3a Antony Chazapis
    if len(hashmap) == 0:
598 6ecccd3a Antony Chazapis
        return hexlify(subhash(''))
599 6ecccd3a Antony Chazapis
    if len(hashmap) == 1:
600 6ecccd3a Antony Chazapis
        return hexlify(subhash(hashmap[0]))
601 6ecccd3a Antony Chazapis
    s = 2
602 6ecccd3a Antony Chazapis
    while s < len(hashmap):
603 6ecccd3a Antony Chazapis
        s = s * 2
604 6ecccd3a Antony Chazapis
    h = hashmap + ([('\x00' * len(hashmap[0]))] * (s - len(hashmap)))
605 49133392 Antony Chazapis
    h = [subhash(h[x] + (h[x + 1] if x + 1 < len(h) else '')) for x in range(0, len(h), 2)]
606 49133392 Antony Chazapis
    while len(h) > 1:
607 49133392 Antony Chazapis
        h = [subhash(h[x] + (h[x + 1] if x + 1 < len(h) else '')) for x in range(0, len(h), 2)]
608 49133392 Antony Chazapis
    return hexlify(h[0])
609 49133392 Antony Chazapis
610 b956618e Antony Chazapis
def update_response_headers(request, response):
611 b956618e Antony Chazapis
    if request.serialization == 'xml':
612 b956618e Antony Chazapis
        response['Content-Type'] = 'application/xml; charset=UTF-8'
613 b956618e Antony Chazapis
    elif request.serialization == 'json':
614 b956618e Antony Chazapis
        response['Content-Type'] = 'application/json; charset=UTF-8'
615 22dab079 Antony Chazapis
    elif not response['Content-Type']:
616 b956618e Antony Chazapis
        response['Content-Type'] = 'text/plain; charset=UTF-8'
617 b956618e Antony Chazapis
618 b956618e Antony Chazapis
    if settings.TEST:
619 b956618e Antony Chazapis
        response['Date'] = format_date_time(time())
620 b956618e Antony Chazapis
621 b956618e Antony Chazapis
def render_fault(request, fault):
622 b956618e Antony Chazapis
    if settings.DEBUG or settings.TEST:
623 b956618e Antony Chazapis
        fault.details = format_exc(fault)
624 b956618e Antony Chazapis
625 b956618e Antony Chazapis
    request.serialization = 'text'
626 b956618e Antony Chazapis
    data = '\n'.join((fault.message, fault.details)) + '\n'
627 b956618e Antony Chazapis
    response = HttpResponse(data, status=fault.code)
628 b956618e Antony Chazapis
    update_response_headers(request, response)
629 b956618e Antony Chazapis
    return response
630 b956618e Antony Chazapis
631 b956618e Antony Chazapis
def request_serialization(request, format_allowed=False):
632 58a6c894 Antony Chazapis
    """Return the serialization format requested.
633 b956618e Antony Chazapis
    
634 b956618e Antony Chazapis
    Valid formats are 'text' and 'json', 'xml' if 'format_allowed' is True.
635 b956618e Antony Chazapis
    """
636 83dd59c5 Antony Chazapis
    
637 b956618e Antony Chazapis
    if not format_allowed:
638 b956618e Antony Chazapis
        return 'text'
639 b956618e Antony Chazapis
    
640 b956618e Antony Chazapis
    format = request.GET.get('format')
641 b956618e Antony Chazapis
    if format == 'json':
642 b956618e Antony Chazapis
        return 'json'
643 b956618e Antony Chazapis
    elif format == 'xml':
644 b956618e Antony Chazapis
        return 'xml'
645 b956618e Antony Chazapis
    
646 b956618e Antony Chazapis
    for item in request.META.get('HTTP_ACCEPT', '').split(','):
647 b956618e Antony Chazapis
        accept, sep, rest = item.strip().partition(';')
648 22dab079 Antony Chazapis
        if accept == 'application/json':
649 b956618e Antony Chazapis
            return 'json'
650 b956618e Antony Chazapis
        elif accept == 'application/xml' or accept == 'text/xml':
651 b956618e Antony Chazapis
            return 'xml'
652 b956618e Antony Chazapis
    
653 b956618e Antony Chazapis
    return 'text'
654 b956618e Antony Chazapis
655 b956618e Antony Chazapis
def api_method(http_method=None, format_allowed=False):
656 58a6c894 Antony Chazapis
    """Decorator function for views that implement an API method."""
657 83dd59c5 Antony Chazapis
    
658 b956618e Antony Chazapis
    def decorator(func):
659 b956618e Antony Chazapis
        @wraps(func)
660 b956618e Antony Chazapis
        def wrapper(request, *args, **kwargs):
661 b956618e Antony Chazapis
            try:
662 b956618e Antony Chazapis
                if http_method and request.method != http_method:
663 b956618e Antony Chazapis
                    raise BadRequest('Method not allowed.')
664 2c22e4ac Antony Chazapis
                
665 b956618e Antony Chazapis
                # The args variable may contain up to (account, container, object).
666 b956618e Antony Chazapis
                if len(args) > 1 and len(args[1]) > 256:
667 b956618e Antony Chazapis
                    raise BadRequest('Container name too large.')
668 b956618e Antony Chazapis
                if len(args) > 2 and len(args[2]) > 1024:
669 b956618e Antony Chazapis
                    raise BadRequest('Object name too large.')
670 b956618e Antony Chazapis
                
671 b956618e Antony Chazapis
                # Fill in custom request variables.
672 b956618e Antony Chazapis
                request.serialization = request_serialization(request, format_allowed)
673 b956618e Antony Chazapis
                # TODO: Authenticate.
674 b956618e Antony Chazapis
                request.user = "test"
675 b956618e Antony Chazapis
                
676 b956618e Antony Chazapis
                response = func(request, *args, **kwargs)
677 b956618e Antony Chazapis
                update_response_headers(request, response)
678 b956618e Antony Chazapis
                return response
679 b956618e Antony Chazapis
            except Fault, fault:
680 b956618e Antony Chazapis
                return render_fault(request, fault)
681 b956618e Antony Chazapis
            except BaseException, e:
682 b956618e Antony Chazapis
                logger.exception('Unexpected error: %s' % e)
683 b956618e Antony Chazapis
                fault = ServiceUnavailable('Unexpected error')
684 b956618e Antony Chazapis
                return render_fault(request, fault)
685 b956618e Antony Chazapis
        return wrapper
686 b956618e Antony Chazapis
    return decorator