Statistics
| Branch: | Tag: | Revision:

root / pithos / api / util.py @ 7912c32e

History | View | Annotate | Download (27.4 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 1993fea9 Antony Chazapis
from django.utils import simplejson as json
43 22dab079 Antony Chazapis
from django.utils.http import http_date, parse_etags
44 b956618e Antony Chazapis
45 6d1e6dce Sofia Papagiannaki
from pithos.api.compat import parse_http_date_safe, parse_http_date
46 02c0c3fa Antony Chazapis
from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, ItemNotFound,
47 1c7fc132 Antony Chazapis
                                Conflict, LengthRequired, PreconditionFailed, RangeNotSatisfiable,
48 02c0c3fa Antony Chazapis
                                ServiceUnavailable)
49 b956618e Antony Chazapis
from pithos.backends import backend
50 cca6c617 Antony Chazapis
from pithos.backends.base import NotAllowedError
51 b956618e Antony Chazapis
52 b956618e Antony Chazapis
import datetime
53 b956618e Antony Chazapis
import logging
54 22dab079 Antony Chazapis
import re
55 cbfb6636 Sofia Papagiannaki
import hashlib
56 7bef5750 Antony Chazapis
import uuid
57 b956618e Antony Chazapis
58 b956618e Antony Chazapis
59 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
60 b956618e Antony Chazapis
61 b956618e Antony Chazapis
62 02c0c3fa Antony Chazapis
def printable_header_dict(d):
63 b956618e Antony Chazapis
    """Format a meta dictionary for printing out json/xml.
64 b956618e Antony Chazapis
    
65 b956618e Antony Chazapis
    Convert all keys to lower case and replace dashes to underscores.
66 b956618e Antony Chazapis
    Change 'modified' key from backend to 'last_modified' and format date.
67 b956618e Antony Chazapis
    """
68 83dd59c5 Antony Chazapis
    
69 b956618e Antony Chazapis
    if 'modified' in d:
70 b956618e Antony Chazapis
        d['last_modified'] = datetime.datetime.fromtimestamp(int(d['modified'])).isoformat()
71 b956618e Antony Chazapis
        del(d['modified'])
72 b956618e Antony Chazapis
    return dict([(k.lower().replace('-', '_'), v) for k, v in d.iteritems()])
73 b956618e Antony Chazapis
74 02c0c3fa Antony Chazapis
def format_header_key(k):
75 58a6c894 Antony Chazapis
    """Convert underscores to dashes and capitalize intra-dash strings."""
76 83dd59c5 Antony Chazapis
    
77 b956618e Antony Chazapis
    return '-'.join([x.capitalize() for x in k.replace('_', '-').split('-')])
78 b956618e Antony Chazapis
79 02c0c3fa Antony Chazapis
def get_header_prefix(request, prefix):
80 02c0c3fa Antony Chazapis
    """Get all prefix-* request headers in a dict. Reformat keys with format_header_key()."""
81 83dd59c5 Antony Chazapis
    
82 b956618e Antony Chazapis
    prefix = 'HTTP_' + prefix.upper().replace('-', '_')
83 3ab38c43 Antony Chazapis
    # TODO: Document or remove '~' replacing.
84 3ab38c43 Antony Chazapis
    return dict([(format_header_key(k[5:]), v.replace('~', '')) for k, v in request.META.iteritems() if k.startswith(prefix) and len(k) > len(prefix)])
85 02c0c3fa Antony Chazapis
86 02c0c3fa Antony Chazapis
def get_account_headers(request):
87 02c0c3fa Antony Chazapis
    meta = get_header_prefix(request, 'X-Account-Meta-')
88 02c0c3fa Antony Chazapis
    groups = {}
89 02c0c3fa Antony Chazapis
    for k, v in get_header_prefix(request, 'X-Account-Group-').iteritems():
90 02c0c3fa Antony Chazapis
        n = k[16:].lower()
91 02c0c3fa Antony Chazapis
        if '-' in n or '_' in n:
92 02c0c3fa Antony Chazapis
            raise BadRequest('Bad characters in group name')
93 02c0c3fa Antony Chazapis
        groups[n] = v.replace(' ', '').split(',')
94 02c0c3fa Antony Chazapis
        if '' in groups[n]:
95 02c0c3fa Antony Chazapis
            groups[n].remove('')
96 02c0c3fa Antony Chazapis
    return meta, groups
97 02c0c3fa Antony Chazapis
98 02c0c3fa Antony Chazapis
def put_account_headers(response, meta, groups):
99 f6c97079 Antony Chazapis
    if 'count' in meta:
100 f6c97079 Antony Chazapis
        response['X-Account-Container-Count'] = meta['count']
101 f6c97079 Antony Chazapis
    if 'bytes' in meta:
102 f6c97079 Antony Chazapis
        response['X-Account-Bytes-Used'] = meta['bytes']
103 d065f612 Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
104 b956618e Antony Chazapis
    for k in [x for x in meta.keys() if x.startswith('X-Account-Meta-')]:
105 b956618e Antony Chazapis
        response[k.encode('utf-8')] = meta[k].encode('utf-8')
106 83dd59c5 Antony Chazapis
    if 'until_timestamp' in meta:
107 83dd59c5 Antony Chazapis
        response['X-Account-Until-Timestamp'] = http_date(int(meta['until_timestamp']))
108 02c0c3fa Antony Chazapis
    for k, v in groups.iteritems():
109 02c0c3fa Antony Chazapis
        response[format_header_key('X-Account-Group-' + k).encode('utf-8')] = (','.join(v)).encode('utf-8')
110 b956618e Antony Chazapis
111 02c0c3fa Antony Chazapis
def get_container_headers(request):
112 02c0c3fa Antony Chazapis
    meta = get_header_prefix(request, 'X-Container-Meta-')
113 3ab38c43 Antony Chazapis
    policy = dict([(k[19:].lower(), v.replace(' ', '')) for k, v in get_header_prefix(request, 'X-Container-Policy-').iteritems()])
114 3ab38c43 Antony Chazapis
    return meta, policy
115 b956618e Antony Chazapis
116 3ab38c43 Antony Chazapis
def put_container_headers(response, meta, policy):
117 f6c97079 Antony Chazapis
    if 'count' in meta:
118 f6c97079 Antony Chazapis
        response['X-Container-Object-Count'] = meta['count']
119 f6c97079 Antony Chazapis
    if 'bytes' in meta:
120 f6c97079 Antony Chazapis
        response['X-Container-Bytes-Used'] = meta['bytes']
121 58a6c894 Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
122 b956618e Antony Chazapis
    for k in [x for x in meta.keys() if x.startswith('X-Container-Meta-')]:
123 b956618e Antony Chazapis
        response[k.encode('utf-8')] = meta[k].encode('utf-8')
124 7912c32e Antony Chazapis
    response['X-Container-Object-Meta'] = ','.join([x[14:] for x in meta['object_meta'] if x.startswith('X-Object-Meta-')])
125 6bc372a4 Antony Chazapis
    response['X-Container-Block-Size'] = backend.block_size
126 6bc372a4 Antony Chazapis
    response['X-Container-Block-Hash'] = backend.hash_algorithm
127 83dd59c5 Antony Chazapis
    if 'until_timestamp' in meta:
128 83dd59c5 Antony Chazapis
        response['X-Container-Until-Timestamp'] = http_date(int(meta['until_timestamp']))
129 3ab38c43 Antony Chazapis
    for k, v in policy.iteritems():
130 3ab38c43 Antony Chazapis
        response[format_header_key('X-Container-Policy-' + k).encode('utf-8')] = v.encode('utf-8')
131 b956618e Antony Chazapis
132 02c0c3fa Antony Chazapis
def get_object_headers(request):
133 02c0c3fa Antony Chazapis
    meta = get_header_prefix(request, 'X-Object-Meta-')
134 b956618e Antony Chazapis
    if request.META.get('CONTENT_TYPE'):
135 b956618e Antony Chazapis
        meta['Content-Type'] = request.META['CONTENT_TYPE']
136 b956618e Antony Chazapis
    if request.META.get('HTTP_CONTENT_ENCODING'):
137 b956618e Antony Chazapis
        meta['Content-Encoding'] = request.META['HTTP_CONTENT_ENCODING']
138 22dab079 Antony Chazapis
    if request.META.get('HTTP_CONTENT_DISPOSITION'):
139 22dab079 Antony Chazapis
        meta['Content-Disposition'] = request.META['HTTP_CONTENT_DISPOSITION']
140 b956618e Antony Chazapis
    if request.META.get('HTTP_X_OBJECT_MANIFEST'):
141 b956618e Antony Chazapis
        meta['X-Object-Manifest'] = request.META['HTTP_X_OBJECT_MANIFEST']
142 3ab38c43 Antony Chazapis
    return meta, get_sharing(request), get_public(request)
143 b956618e Antony Chazapis
144 3ab38c43 Antony Chazapis
def put_object_headers(response, meta, restricted=False):
145 b956618e Antony Chazapis
    response['ETag'] = meta['hash']
146 b956618e Antony Chazapis
    response['Content-Length'] = meta['bytes']
147 b956618e Antony Chazapis
    response['Content-Type'] = meta.get('Content-Type', 'application/octet-stream')
148 b956618e Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
149 3ab38c43 Antony Chazapis
    if not restricted:
150 104626e3 Antony Chazapis
        response['X-Object-Modified-By'] = meta['modified_by']
151 7bef5750 Antony Chazapis
        response['X-Object-Version'] = meta['version']
152 104626e3 Antony Chazapis
        response['X-Object-Version-Timestamp'] = http_date(int(meta['version_timestamp']))
153 7bef5750 Antony Chazapis
        for k in [x for x in meta.keys() if x.startswith('X-Object-Meta-')]:
154 7bef5750 Antony Chazapis
            response[k.encode('utf-8')] = meta[k].encode('utf-8')
155 e0f916bb Antony Chazapis
        for k in ('Content-Encoding', 'Content-Disposition', 'X-Object-Manifest', 'X-Object-Sharing', 'X-Object-Shared-By', 'X-Object-Public'):
156 7bef5750 Antony Chazapis
            if k in meta:
157 7bef5750 Antony Chazapis
                response[k] = meta[k]
158 7bef5750 Antony Chazapis
    else:
159 c9af0703 Antony Chazapis
        for k in ('Content-Encoding', 'Content-Disposition'):
160 7bef5750 Antony Chazapis
            if k in meta:
161 7bef5750 Antony Chazapis
                response[k] = meta[k]
162 b956618e Antony Chazapis
163 8cb45c13 Antony Chazapis
def update_manifest_meta(request, v_account, meta):
164 8cb45c13 Antony Chazapis
    """Update metadata if the object has an X-Object-Manifest."""
165 8cb45c13 Antony Chazapis
    
166 8cb45c13 Antony Chazapis
    if 'X-Object-Manifest' in meta:
167 8cb45c13 Antony Chazapis
        hash = ''
168 8cb45c13 Antony Chazapis
        bytes = 0
169 8cb45c13 Antony Chazapis
        try:
170 6d817842 Antony Chazapis
            src_container, src_name = split_container_object_string('/' + meta['X-Object-Manifest'])
171 8cb45c13 Antony Chazapis
            objects = backend.list_objects(request.user, v_account, src_container, prefix=src_name, virtual=False)
172 8cb45c13 Antony Chazapis
            for x in objects:
173 8cb45c13 Antony Chazapis
                src_meta = backend.get_object_meta(request.user, v_account, src_container, x[0], x[1])
174 8cb45c13 Antony Chazapis
                hash += src_meta['hash']
175 8cb45c13 Antony Chazapis
                bytes += src_meta['bytes']
176 8cb45c13 Antony Chazapis
        except:
177 8cb45c13 Antony Chazapis
            # Ignore errors.
178 8cb45c13 Antony Chazapis
            return
179 8cb45c13 Antony Chazapis
        meta['bytes'] = bytes
180 8cb45c13 Antony Chazapis
        md5 = hashlib.md5()
181 8cb45c13 Antony Chazapis
        md5.update(hash)
182 8cb45c13 Antony Chazapis
        meta['hash'] = md5.hexdigest().lower()
183 8cb45c13 Antony Chazapis
184 cca6c617 Antony Chazapis
def update_sharing_meta(permissions, v_account, v_container, v_object, meta):
185 cca6c617 Antony Chazapis
    if permissions is None:
186 cca6c617 Antony Chazapis
        return
187 cca6c617 Antony Chazapis
    perm_path, perms = permissions
188 cca6c617 Antony Chazapis
    if len(perms) == 0:
189 cca6c617 Antony Chazapis
        return
190 3436eeb0 Antony Chazapis
    ret = []
191 cca6c617 Antony Chazapis
    r = ','.join(perms.get('read', []))
192 3436eeb0 Antony Chazapis
    if r:
193 3436eeb0 Antony Chazapis
        ret.append('read=' + r)
194 cca6c617 Antony Chazapis
    w = ','.join(perms.get('write', []))
195 3436eeb0 Antony Chazapis
    if w:
196 3436eeb0 Antony Chazapis
        ret.append('write=' + w)
197 cca6c617 Antony Chazapis
    meta['X-Object-Sharing'] = '; '.join(ret)
198 cca6c617 Antony Chazapis
    if '/'.join((v_account, v_container, v_object)) != perm_path:
199 cca6c617 Antony Chazapis
        meta['X-Object-Shared-By'] = perm_path
200 3436eeb0 Antony Chazapis
201 e0f916bb Antony Chazapis
def update_public_meta(public, meta):
202 e0f916bb Antony Chazapis
    if not public:
203 e0f916bb Antony Chazapis
        return
204 e0f916bb Antony Chazapis
    meta['X-Object-Public'] = public
205 e0f916bb Antony Chazapis
206 b956618e Antony Chazapis
def validate_modification_preconditions(request, meta):
207 58a6c894 Antony Chazapis
    """Check that the modified timestamp conforms with the preconditions set."""
208 83dd59c5 Antony Chazapis
    
209 22dab079 Antony Chazapis
    if 'modified' not in meta:
210 22dab079 Antony Chazapis
        return # TODO: Always return?
211 22dab079 Antony Chazapis
    
212 b956618e Antony Chazapis
    if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
213 b956618e Antony Chazapis
    if if_modified_since is not None:
214 b956618e Antony Chazapis
        if_modified_since = parse_http_date_safe(if_modified_since)
215 22dab079 Antony Chazapis
    if if_modified_since is not None and int(meta['modified']) <= if_modified_since:
216 3e5d2f38 Antony Chazapis
        raise NotModified('Resource has not been modified')
217 b956618e Antony Chazapis
    
218 b956618e Antony Chazapis
    if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')
219 b956618e Antony Chazapis
    if if_unmodified_since is not None:
220 b956618e Antony Chazapis
        if_unmodified_since = parse_http_date_safe(if_unmodified_since)
221 22dab079 Antony Chazapis
    if if_unmodified_since is not None and int(meta['modified']) > if_unmodified_since:
222 3e5d2f38 Antony Chazapis
        raise PreconditionFailed('Resource has been modified')
223 b956618e Antony Chazapis
224 22dab079 Antony Chazapis
def validate_matching_preconditions(request, meta):
225 58a6c894 Antony Chazapis
    """Check that the ETag conforms with the preconditions set."""
226 83dd59c5 Antony Chazapis
    
227 22dab079 Antony Chazapis
    if 'hash' not in meta:
228 22dab079 Antony Chazapis
        return # TODO: Always return?
229 22dab079 Antony Chazapis
    
230 22dab079 Antony Chazapis
    if_match = request.META.get('HTTP_IF_MATCH')
231 22dab079 Antony Chazapis
    if if_match is not None and if_match != '*':
232 22dab079 Antony Chazapis
        if meta['hash'] not in [x.lower() for x in parse_etags(if_match)]:
233 3e5d2f38 Antony Chazapis
            raise PreconditionFailed('Resource Etag does not match')
234 22dab079 Antony Chazapis
    
235 22dab079 Antony Chazapis
    if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
236 22dab079 Antony Chazapis
    if if_none_match is not None:
237 22dab079 Antony Chazapis
        if if_none_match == '*' or meta['hash'] in [x.lower() for x in parse_etags(if_none_match)]:
238 3e5d2f38 Antony Chazapis
            raise NotModified('Resource Etag matches')
239 22dab079 Antony Chazapis
240 83dd59c5 Antony Chazapis
def split_container_object_string(s):
241 6d817842 Antony Chazapis
    if not len(s) > 0 or s[0] != '/':
242 6d817842 Antony Chazapis
        raise ValueError
243 6d817842 Antony Chazapis
    s = s[1:]
244 8cb45c13 Antony Chazapis
    pos = s.find('/')
245 8cb45c13 Antony Chazapis
    if pos == -1:
246 83dd59c5 Antony Chazapis
        raise ValueError
247 8cb45c13 Antony Chazapis
    return s[:pos], s[(pos + 1):]
248 83dd59c5 Antony Chazapis
249 83dd59c5 Antony Chazapis
def copy_or_move_object(request, v_account, src_container, src_name, dest_container, dest_name, move=False):
250 58a6c894 Antony Chazapis
    """Copy or move an object."""
251 22dab079 Antony Chazapis
    
252 3ab38c43 Antony Chazapis
    meta, permissions, public = get_object_headers(request)
253 a6eb13e9 Antony Chazapis
    src_version = request.META.get('HTTP_X_SOURCE_VERSION')    
254 b956618e Antony Chazapis
    try:
255 b956618e Antony Chazapis
        if move:
256 a6eb13e9 Antony Chazapis
            backend.move_object(request.user, v_account, src_container, src_name, dest_container, dest_name, meta, False, permissions)
257 b956618e Antony Chazapis
        else:
258 a6eb13e9 Antony Chazapis
            backend.copy_object(request.user, v_account, src_container, src_name, dest_container, dest_name, meta, False, permissions, src_version)
259 cca6c617 Antony Chazapis
    except NotAllowedError:
260 cca6c617 Antony Chazapis
        raise Unauthorized('Access denied')
261 e8886082 Antony Chazapis
    except NameError, IndexError:
262 b956618e Antony Chazapis
        raise ItemNotFound('Container or object does not exist')
263 3436eeb0 Antony Chazapis
    except ValueError:
264 3436eeb0 Antony Chazapis
        raise BadRequest('Invalid sharing header')
265 1993fea9 Antony Chazapis
    except AttributeError, e:
266 1993fea9 Antony Chazapis
        raise Conflict(json.dumps(e.data))
267 e0f916bb Antony Chazapis
    if public is not None:
268 e0f916bb Antony Chazapis
        try:
269 ab2e317e Antony Chazapis
            backend.update_object_public(request.user, v_account, dest_container, dest_name, public)
270 e0f916bb Antony Chazapis
        except NotAllowedError:
271 e0f916bb Antony Chazapis
            raise Unauthorized('Access denied')
272 e0f916bb Antony Chazapis
        except NameError:
273 e0f916bb Antony Chazapis
            raise ItemNotFound('Object does not exist')
274 b956618e Antony Chazapis
275 1495b972 Antony Chazapis
def get_int_parameter(p):
276 83dd59c5 Antony Chazapis
    if p is not None:
277 58a6c894 Antony Chazapis
        try:
278 83dd59c5 Antony Chazapis
            p = int(p)
279 58a6c894 Antony Chazapis
        except ValueError:
280 58a6c894 Antony Chazapis
            return None
281 83dd59c5 Antony Chazapis
        if p < 0:
282 58a6c894 Antony Chazapis
            return None
283 83dd59c5 Antony Chazapis
    return p
284 58a6c894 Antony Chazapis
285 22dab079 Antony Chazapis
def get_content_length(request):
286 1495b972 Antony Chazapis
    content_length = get_int_parameter(request.META.get('CONTENT_LENGTH'))
287 1495b972 Antony Chazapis
    if content_length is None:
288 1495b972 Antony Chazapis
        raise LengthRequired('Missing or invalid Content-Length header')
289 22dab079 Antony Chazapis
    return content_length
290 22dab079 Antony Chazapis
291 22dab079 Antony Chazapis
def get_range(request, size):
292 58a6c894 Antony Chazapis
    """Parse a Range header from the request.
293 b956618e Antony Chazapis
    
294 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
295 22dab079 Antony Chazapis
    or a list of (offset, length) tuples - should be further checked.
296 b956618e Antony Chazapis
    """
297 83dd59c5 Antony Chazapis
    
298 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_RANGE', '').replace(' ', '')
299 22dab079 Antony Chazapis
    if not ranges.startswith('bytes='):
300 b956618e Antony Chazapis
        return None
301 b956618e Antony Chazapis
    
302 22dab079 Antony Chazapis
    ret = []
303 22dab079 Antony Chazapis
    for r in (x.strip() for x in ranges[6:].split(',')):
304 22dab079 Antony Chazapis
        p = re.compile('^(?P<offset>\d*)-(?P<upto>\d*)$')
305 22dab079 Antony Chazapis
        m = p.match(r)
306 22dab079 Antony Chazapis
        if not m:
307 22dab079 Antony Chazapis
            return None
308 22dab079 Antony Chazapis
        offset = m.group('offset')
309 22dab079 Antony Chazapis
        upto = m.group('upto')
310 22dab079 Antony Chazapis
        if offset == '' and upto == '':
311 b956618e Antony Chazapis
            return None
312 b956618e Antony Chazapis
        
313 22dab079 Antony Chazapis
        if offset != '':
314 22dab079 Antony Chazapis
            offset = int(offset)
315 22dab079 Antony Chazapis
            if upto != '':
316 b956618e Antony Chazapis
                upto = int(upto)
317 22dab079 Antony Chazapis
                if offset > upto:
318 22dab079 Antony Chazapis
                    return None
319 22dab079 Antony Chazapis
                ret.append((offset, upto - offset + 1))
320 22dab079 Antony Chazapis
            else:
321 22dab079 Antony Chazapis
                ret.append((offset, size - offset))
322 b956618e Antony Chazapis
        else:
323 22dab079 Antony Chazapis
            length = int(upto)
324 22dab079 Antony Chazapis
            ret.append((size - length, length))
325 22dab079 Antony Chazapis
    
326 22dab079 Antony Chazapis
    return ret
327 22dab079 Antony Chazapis
328 22dab079 Antony Chazapis
def get_content_range(request):
329 58a6c894 Antony Chazapis
    """Parse a Content-Range header from the request.
330 22dab079 Antony Chazapis
    
331 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
332 22dab079 Antony Chazapis
    or an (offset, length, total) tuple - check as length, total may be None.
333 22dab079 Antony Chazapis
    Returns (None, None, None) if the provided range is '*/*'.
334 22dab079 Antony Chazapis
    """
335 22dab079 Antony Chazapis
    
336 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_CONTENT_RANGE', '')
337 22dab079 Antony Chazapis
    if not ranges:
338 22dab079 Antony Chazapis
        return None
339 22dab079 Antony Chazapis
    
340 22dab079 Antony Chazapis
    p = re.compile('^bytes (?P<offset>\d+)-(?P<upto>\d*)/(?P<total>(\d+|\*))$')
341 22dab079 Antony Chazapis
    m = p.match(ranges)
342 22dab079 Antony Chazapis
    if not m:
343 22dab079 Antony Chazapis
        if ranges == 'bytes */*':
344 22dab079 Antony Chazapis
            return (None, None, None)
345 22dab079 Antony Chazapis
        return None
346 22dab079 Antony Chazapis
    offset = int(m.group('offset'))
347 22dab079 Antony Chazapis
    upto = m.group('upto')
348 22dab079 Antony Chazapis
    total = m.group('total')
349 22dab079 Antony Chazapis
    if upto != '':
350 22dab079 Antony Chazapis
        upto = int(upto)
351 b956618e Antony Chazapis
    else:
352 22dab079 Antony Chazapis
        upto = None
353 22dab079 Antony Chazapis
    if total != '*':
354 22dab079 Antony Chazapis
        total = int(total)
355 22dab079 Antony Chazapis
    else:
356 22dab079 Antony Chazapis
        total = None
357 70e526a0 Antony Chazapis
    if (upto is not None and offset > upto) or \
358 70e526a0 Antony Chazapis
        (total is not None and offset >= total) or \
359 70e526a0 Antony Chazapis
        (total is not None and upto is not None and upto >= total):
360 22dab079 Antony Chazapis
        return None
361 22dab079 Antony Chazapis
    
362 70e526a0 Antony Chazapis
    if upto is None:
363 22dab079 Antony Chazapis
        length = None
364 22dab079 Antony Chazapis
    else:
365 22dab079 Antony Chazapis
        length = upto - offset + 1
366 22dab079 Antony Chazapis
    return (offset, length, total)
367 b956618e Antony Chazapis
368 3436eeb0 Antony Chazapis
def get_sharing(request):
369 3436eeb0 Antony Chazapis
    """Parse an X-Object-Sharing header from the request.
370 3436eeb0 Antony Chazapis
    
371 3436eeb0 Antony Chazapis
    Raises BadRequest on error.
372 3436eeb0 Antony Chazapis
    """
373 3436eeb0 Antony Chazapis
    
374 3436eeb0 Antony Chazapis
    permissions = request.META.get('HTTP_X_OBJECT_SHARING')
375 cca6c617 Antony Chazapis
    if permissions is None:
376 3436eeb0 Antony Chazapis
        return None
377 3436eeb0 Antony Chazapis
    
378 3436eeb0 Antony Chazapis
    ret = {}
379 cca6c617 Antony Chazapis
    permissions = permissions.replace(' ', '')
380 cca6c617 Antony Chazapis
    if permissions == '':
381 cca6c617 Antony Chazapis
        return ret
382 cca6c617 Antony Chazapis
    for perm in (x for x in permissions.split(';')):
383 cca6c617 Antony Chazapis
        if perm.startswith('read='):
384 b0a2d1a6 Antony Chazapis
            ret['read'] = list(set([v.replace(' ','').lower() for v in perm[5:].split(',')]))
385 02c0c3fa Antony Chazapis
            if '' in ret['read']:
386 02c0c3fa Antony Chazapis
                ret['read'].remove('')
387 e8886082 Antony Chazapis
            if '*' in ret['read']:
388 e8886082 Antony Chazapis
                ret['read'] = ['*']
389 3436eeb0 Antony Chazapis
            if len(ret['read']) == 0:
390 3436eeb0 Antony Chazapis
                raise BadRequest('Bad X-Object-Sharing header value')
391 3436eeb0 Antony Chazapis
        elif perm.startswith('write='):
392 b0a2d1a6 Antony Chazapis
            ret['write'] = list(set([v.replace(' ','').lower() for v in perm[6:].split(',')]))
393 02c0c3fa Antony Chazapis
            if '' in ret['write']:
394 02c0c3fa Antony Chazapis
                ret['write'].remove('')
395 e8886082 Antony Chazapis
            if '*' in ret['write']:
396 e8886082 Antony Chazapis
                ret['write'] = ['*']
397 3436eeb0 Antony Chazapis
            if len(ret['write']) == 0:
398 3436eeb0 Antony Chazapis
                raise BadRequest('Bad X-Object-Sharing header value')
399 3436eeb0 Antony Chazapis
        else:
400 3436eeb0 Antony Chazapis
            raise BadRequest('Bad X-Object-Sharing header value')
401 3436eeb0 Antony Chazapis
    return ret
402 3436eeb0 Antony Chazapis
403 3ab38c43 Antony Chazapis
def get_public(request):
404 3ab38c43 Antony Chazapis
    """Parse an X-Object-Public header from the request.
405 3ab38c43 Antony Chazapis
    
406 3ab38c43 Antony Chazapis
    Raises BadRequest on error.
407 3ab38c43 Antony Chazapis
    """
408 3ab38c43 Antony Chazapis
    
409 3ab38c43 Antony Chazapis
    public = request.META.get('HTTP_X_OBJECT_PUBLIC')
410 3ab38c43 Antony Chazapis
    if public is None:
411 3ab38c43 Antony Chazapis
        return None
412 3ab38c43 Antony Chazapis
    
413 3ab38c43 Antony Chazapis
    public = public.replace(' ', '').lower()
414 3ab38c43 Antony Chazapis
    if public == 'true':
415 3ab38c43 Antony Chazapis
        return True
416 3ab38c43 Antony Chazapis
    elif public == 'false' or public == '':
417 3ab38c43 Antony Chazapis
        return False
418 3ab38c43 Antony Chazapis
    raise BadRequest('Bad X-Object-Public header value')
419 3ab38c43 Antony Chazapis
420 b956618e Antony Chazapis
def raw_input_socket(request):
421 58a6c894 Antony Chazapis
    """Return the socket for reading the rest of the request."""
422 83dd59c5 Antony Chazapis
    
423 b956618e Antony Chazapis
    server_software = request.META.get('SERVER_SOFTWARE')
424 b956618e Antony Chazapis
    if not server_software:
425 b956618e Antony Chazapis
        if 'wsgi.input' in request.environ:
426 b956618e Antony Chazapis
            return request.environ['wsgi.input']
427 b956618e Antony Chazapis
        raise ServiceUnavailable('Unknown server software')
428 b956618e Antony Chazapis
    if server_software.startswith('WSGIServer'):
429 b956618e Antony Chazapis
        return request.environ['wsgi.input']
430 b956618e Antony Chazapis
    elif server_software.startswith('mod_python'):
431 b956618e Antony Chazapis
        return request._req
432 b956618e Antony Chazapis
    raise ServiceUnavailable('Unknown server software')
433 b956618e Antony Chazapis
434 b956618e Antony Chazapis
MAX_UPLOAD_SIZE = 10 * (1024 * 1024) # 10MB
435 b956618e Antony Chazapis
436 22dab079 Antony Chazapis
def socket_read_iterator(sock, length=0, blocksize=4096):
437 58a6c894 Antony Chazapis
    """Return a maximum of blocksize data read from the socket in each iteration.
438 b956618e Antony Chazapis
    
439 22dab079 Antony Chazapis
    Read up to 'length'. If 'length' is negative, will attempt a chunked read.
440 b956618e Antony Chazapis
    The maximum ammount of data read is controlled by MAX_UPLOAD_SIZE.
441 b956618e Antony Chazapis
    """
442 83dd59c5 Antony Chazapis
    
443 b956618e Antony Chazapis
    if length < 0: # Chunked transfers
444 22dab079 Antony Chazapis
        data = ''
445 b956618e Antony Chazapis
        while length < MAX_UPLOAD_SIZE:
446 22dab079 Antony Chazapis
            # Get chunk size.
447 22dab079 Antony Chazapis
            if hasattr(sock, 'readline'):
448 22dab079 Antony Chazapis
                chunk_length = sock.readline()
449 22dab079 Antony Chazapis
            else:
450 22dab079 Antony Chazapis
                chunk_length = ''
451 22dab079 Antony Chazapis
                while chunk_length[-1:] != '\n':
452 22dab079 Antony Chazapis
                    chunk_length += sock.read(1)
453 22dab079 Antony Chazapis
                chunk_length.strip()
454 b956618e Antony Chazapis
            pos = chunk_length.find(';')
455 b956618e Antony Chazapis
            if pos >= 0:
456 b956618e Antony Chazapis
                chunk_length = chunk_length[:pos]
457 b956618e Antony Chazapis
            try:
458 b956618e Antony Chazapis
                chunk_length = int(chunk_length, 16)
459 b956618e Antony Chazapis
            except Exception, e:
460 b956618e Antony Chazapis
                raise BadRequest('Bad chunk size') # TODO: Change to something more appropriate.
461 22dab079 Antony Chazapis
            # Check if done.
462 b956618e Antony Chazapis
            if chunk_length == 0:
463 22dab079 Antony Chazapis
                if len(data) > 0:
464 22dab079 Antony Chazapis
                    yield data
465 b956618e Antony Chazapis
                return
466 22dab079 Antony Chazapis
            # Get the actual data.
467 b956618e Antony Chazapis
            while chunk_length > 0:
468 22dab079 Antony Chazapis
                chunk = sock.read(min(chunk_length, blocksize))
469 22dab079 Antony Chazapis
                chunk_length -= len(chunk)
470 623a0cf4 Sofia Papagiannaki
                if length > 0:
471 623a0cf4 Sofia Papagiannaki
                    length += len(chunk)
472 22dab079 Antony Chazapis
                data += chunk
473 22dab079 Antony Chazapis
                if len(data) >= blocksize:
474 22dab079 Antony Chazapis
                    ret = data[:blocksize]
475 22dab079 Antony Chazapis
                    data = data[blocksize:]
476 22dab079 Antony Chazapis
                    yield ret
477 22dab079 Antony Chazapis
            sock.read(2) # CRLF
478 32a437b1 Sofia Papagiannaki
        raise BadRequest('Maximum size is reached')
479 b956618e Antony Chazapis
    else:
480 b956618e Antony Chazapis
        if length > MAX_UPLOAD_SIZE:
481 32a437b1 Sofia Papagiannaki
            raise BadRequest('Maximum size is reached')
482 b956618e Antony Chazapis
        while length > 0:
483 b956618e Antony Chazapis
            data = sock.read(min(length, blocksize))
484 b956618e Antony Chazapis
            length -= len(data)
485 b956618e Antony Chazapis
            yield data
486 b956618e Antony Chazapis
487 22dab079 Antony Chazapis
class ObjectWrapper(object):
488 58a6c894 Antony Chazapis
    """Return the object's data block-per-block in each iteration.
489 22dab079 Antony Chazapis
    
490 22dab079 Antony Chazapis
    Read from the object using the offset and length provided in each entry of the range list.
491 22dab079 Antony Chazapis
    """
492 22dab079 Antony Chazapis
    
493 8cb45c13 Antony Chazapis
    def __init__(self, ranges, sizes, hashmaps, boundary):
494 22dab079 Antony Chazapis
        self.ranges = ranges
495 8cb45c13 Antony Chazapis
        self.sizes = sizes
496 8cb45c13 Antony Chazapis
        self.hashmaps = hashmaps
497 22dab079 Antony Chazapis
        self.boundary = boundary
498 8cb45c13 Antony Chazapis
        self.size = sum(self.sizes)
499 22dab079 Antony Chazapis
        
500 8cb45c13 Antony Chazapis
        self.file_index = 0
501 8cb45c13 Antony Chazapis
        self.block_index = 0
502 8cb45c13 Antony Chazapis
        self.block_hash = -1
503 22dab079 Antony Chazapis
        self.block = ''
504 22dab079 Antony Chazapis
        
505 22dab079 Antony Chazapis
        self.range_index = -1
506 22dab079 Antony Chazapis
        self.offset, self.length = self.ranges[0]
507 22dab079 Antony Chazapis
    
508 22dab079 Antony Chazapis
    def __iter__(self):
509 22dab079 Antony Chazapis
        return self
510 22dab079 Antony Chazapis
    
511 22dab079 Antony Chazapis
    def part_iterator(self):
512 22dab079 Antony Chazapis
        if self.length > 0:
513 8cb45c13 Antony Chazapis
            # Get the file for the current offset.
514 8cb45c13 Antony Chazapis
            file_size = self.sizes[self.file_index]
515 8cb45c13 Antony Chazapis
            while self.offset >= file_size:
516 8cb45c13 Antony Chazapis
                self.offset -= file_size
517 8cb45c13 Antony Chazapis
                self.file_index += 1
518 8cb45c13 Antony Chazapis
                file_size = self.sizes[self.file_index]
519 8cb45c13 Antony Chazapis
            
520 8cb45c13 Antony Chazapis
            # Get the block for the current position.
521 8cb45c13 Antony Chazapis
            self.block_index = int(self.offset / backend.block_size)
522 8cb45c13 Antony Chazapis
            if self.block_hash != self.hashmaps[self.file_index][self.block_index]:
523 8cb45c13 Antony Chazapis
                self.block_hash = self.hashmaps[self.file_index][self.block_index]
524 22dab079 Antony Chazapis
                try:
525 8cb45c13 Antony Chazapis
                    self.block = backend.get_block(self.block_hash)
526 22dab079 Antony Chazapis
                except NameError:
527 22dab079 Antony Chazapis
                    raise ItemNotFound('Block does not exist')
528 8cb45c13 Antony Chazapis
            
529 22dab079 Antony Chazapis
            # Get the data from the block.
530 22dab079 Antony Chazapis
            bo = self.offset % backend.block_size
531 8cb45c13 Antony Chazapis
            bl = min(self.length, len(self.block) - bo)
532 22dab079 Antony Chazapis
            data = self.block[bo:bo + bl]
533 22dab079 Antony Chazapis
            self.offset += bl
534 22dab079 Antony Chazapis
            self.length -= bl
535 22dab079 Antony Chazapis
            return data
536 22dab079 Antony Chazapis
        else:
537 22dab079 Antony Chazapis
            raise StopIteration
538 22dab079 Antony Chazapis
    
539 22dab079 Antony Chazapis
    def next(self):
540 22dab079 Antony Chazapis
        if len(self.ranges) == 1:
541 22dab079 Antony Chazapis
            return self.part_iterator()
542 22dab079 Antony Chazapis
        if self.range_index == len(self.ranges):
543 22dab079 Antony Chazapis
            raise StopIteration
544 22dab079 Antony Chazapis
        try:
545 22dab079 Antony Chazapis
            if self.range_index == -1:
546 22dab079 Antony Chazapis
                raise StopIteration
547 22dab079 Antony Chazapis
            return self.part_iterator()
548 22dab079 Antony Chazapis
        except StopIteration:
549 22dab079 Antony Chazapis
            self.range_index += 1
550 22dab079 Antony Chazapis
            out = []
551 22dab079 Antony Chazapis
            if self.range_index < len(self.ranges):
552 22dab079 Antony Chazapis
                # Part header.
553 22dab079 Antony Chazapis
                self.offset, self.length = self.ranges[self.range_index]
554 8cb45c13 Antony Chazapis
                self.file_index = 0
555 22dab079 Antony Chazapis
                if self.range_index > 0:
556 22dab079 Antony Chazapis
                    out.append('')
557 22dab079 Antony Chazapis
                out.append('--' + self.boundary)
558 22dab079 Antony Chazapis
                out.append('Content-Range: bytes %d-%d/%d' % (self.offset, self.offset + self.length - 1, self.size))
559 22dab079 Antony Chazapis
                out.append('Content-Transfer-Encoding: binary')
560 22dab079 Antony Chazapis
                out.append('')
561 22dab079 Antony Chazapis
                out.append('')
562 22dab079 Antony Chazapis
                return '\r\n'.join(out)
563 22dab079 Antony Chazapis
            else:
564 22dab079 Antony Chazapis
                # Footer.
565 22dab079 Antony Chazapis
                out.append('')
566 22dab079 Antony Chazapis
                out.append('--' + self.boundary + '--')
567 22dab079 Antony Chazapis
                out.append('')
568 22dab079 Antony Chazapis
                return '\r\n'.join(out)
569 22dab079 Antony Chazapis
570 8cb45c13 Antony Chazapis
def object_data_response(request, sizes, hashmaps, meta, public=False):
571 7bef5750 Antony Chazapis
    """Get the HttpResponse object for replying with the object's data."""
572 7bef5750 Antony Chazapis
    
573 7bef5750 Antony Chazapis
    # Range handling.
574 8cb45c13 Antony Chazapis
    size = sum(sizes)
575 7bef5750 Antony Chazapis
    ranges = get_range(request, size)
576 7bef5750 Antony Chazapis
    if ranges is None:
577 7bef5750 Antony Chazapis
        ranges = [(0, size)]
578 7bef5750 Antony Chazapis
        ret = 200
579 7bef5750 Antony Chazapis
    else:
580 7bef5750 Antony Chazapis
        check = [True for offset, length in ranges if
581 7bef5750 Antony Chazapis
                    length <= 0 or length > size or
582 7bef5750 Antony Chazapis
                    offset < 0 or offset >= size or
583 7bef5750 Antony Chazapis
                    offset + length > size]
584 7bef5750 Antony Chazapis
        if len(check) > 0:
585 6d1e6dce Sofia Papagiannaki
            raise RangeNotSatisfiable('Requested range exceeds object limits')
586 7bef5750 Antony Chazapis
        ret = 206
587 15d465b8 Antony Chazapis
        if_range = request.META.get('HTTP_IF_RANGE')
588 15d465b8 Antony Chazapis
        if if_range:
589 6d1e6dce Sofia Papagiannaki
            try:
590 15d465b8 Antony Chazapis
                # Modification time has passed instead.
591 6d1e6dce Sofia Papagiannaki
                last_modified = parse_http_date(if_range)
592 6d1e6dce Sofia Papagiannaki
                if last_modified != meta['modified']:
593 6d1e6dce Sofia Papagiannaki
                    ranges = [(0, size)]
594 6d1e6dce Sofia Papagiannaki
                    ret = 200
595 6d1e6dce Sofia Papagiannaki
            except ValueError:
596 6d1e6dce Sofia Papagiannaki
                if if_range != meta['hash']:
597 6d1e6dce Sofia Papagiannaki
                    ranges = [(0, size)]
598 6d1e6dce Sofia Papagiannaki
                    ret = 200
599 7bef5750 Antony Chazapis
    
600 7bef5750 Antony Chazapis
    if ret == 206 and len(ranges) > 1:
601 7bef5750 Antony Chazapis
        boundary = uuid.uuid4().hex
602 7bef5750 Antony Chazapis
    else:
603 7bef5750 Antony Chazapis
        boundary = ''
604 8cb45c13 Antony Chazapis
    wrapper = ObjectWrapper(ranges, sizes, hashmaps, boundary)
605 7bef5750 Antony Chazapis
    response = HttpResponse(wrapper, status=ret)
606 02c0c3fa Antony Chazapis
    put_object_headers(response, meta, public)
607 7bef5750 Antony Chazapis
    if ret == 206:
608 7bef5750 Antony Chazapis
        if len(ranges) == 1:
609 7bef5750 Antony Chazapis
            offset, length = ranges[0]
610 7bef5750 Antony Chazapis
            response['Content-Length'] = length # Update with the correct length.
611 7bef5750 Antony Chazapis
            response['Content-Range'] = 'bytes %d-%d/%d' % (offset, offset + length - 1, size)
612 7bef5750 Antony Chazapis
        else:
613 7bef5750 Antony Chazapis
            del(response['Content-Length'])
614 7bef5750 Antony Chazapis
            response['Content-Type'] = 'multipart/byteranges; boundary=%s' % (boundary,)
615 7bef5750 Antony Chazapis
    return response
616 7bef5750 Antony Chazapis
617 cb146cf9 Antony Chazapis
def put_object_block(hashmap, data, offset):
618 cb146cf9 Antony Chazapis
    """Put one block of data at the given offset."""
619 cb146cf9 Antony Chazapis
    
620 cb146cf9 Antony Chazapis
    bi = int(offset / backend.block_size)
621 cb146cf9 Antony Chazapis
    bo = offset % backend.block_size
622 cb146cf9 Antony Chazapis
    bl = min(len(data), backend.block_size - bo)
623 cb146cf9 Antony Chazapis
    if bi < len(hashmap):
624 cb146cf9 Antony Chazapis
        hashmap[bi] = backend.update_block(hashmap[bi], data[:bl], bo)
625 cb146cf9 Antony Chazapis
    else:
626 cb146cf9 Antony Chazapis
        hashmap.append(backend.put_block(('\x00' * bo) + data[:bl]))
627 cb146cf9 Antony Chazapis
    return bl # Return ammount of data written.
628 cb146cf9 Antony Chazapis
629 49133392 Antony Chazapis
def hashmap_hash(hashmap):
630 58a6c894 Antony Chazapis
    """Produce the root hash, treating the hashmap as a Merkle-like tree."""
631 49133392 Antony Chazapis
    
632 49133392 Antony Chazapis
    def subhash(d):
633 49133392 Antony Chazapis
        h = hashlib.new(backend.hash_algorithm)
634 49133392 Antony Chazapis
        h.update(d)
635 49133392 Antony Chazapis
        return h.digest()
636 49133392 Antony Chazapis
    
637 6ecccd3a Antony Chazapis
    if len(hashmap) == 0:
638 6ecccd3a Antony Chazapis
        return hexlify(subhash(''))
639 6ecccd3a Antony Chazapis
    if len(hashmap) == 1:
640 6ecccd3a Antony Chazapis
        return hexlify(subhash(hashmap[0]))
641 6ecccd3a Antony Chazapis
    s = 2
642 6ecccd3a Antony Chazapis
    while s < len(hashmap):
643 6ecccd3a Antony Chazapis
        s = s * 2
644 6ecccd3a Antony Chazapis
    h = hashmap + ([('\x00' * len(hashmap[0]))] * (s - len(hashmap)))
645 49133392 Antony Chazapis
    h = [subhash(h[x] + (h[x + 1] if x + 1 < len(h) else '')) for x in range(0, len(h), 2)]
646 49133392 Antony Chazapis
    while len(h) > 1:
647 49133392 Antony Chazapis
        h = [subhash(h[x] + (h[x + 1] if x + 1 < len(h) else '')) for x in range(0, len(h), 2)]
648 49133392 Antony Chazapis
    return hexlify(h[0])
649 49133392 Antony Chazapis
650 b956618e Antony Chazapis
def update_response_headers(request, response):
651 b956618e Antony Chazapis
    if request.serialization == 'xml':
652 b956618e Antony Chazapis
        response['Content-Type'] = 'application/xml; charset=UTF-8'
653 b956618e Antony Chazapis
    elif request.serialization == 'json':
654 b956618e Antony Chazapis
        response['Content-Type'] = 'application/json; charset=UTF-8'
655 22dab079 Antony Chazapis
    elif not response['Content-Type']:
656 b956618e Antony Chazapis
        response['Content-Type'] = 'text/plain; charset=UTF-8'
657 b980169d Antony Chazapis
    
658 b980169d Antony Chazapis
    if not response.has_header('Content-Length') and not (response.has_header('Content-Type') and response['Content-Type'].startswith('multipart/byteranges')):
659 b980169d Antony Chazapis
        response['Content-Length'] = len(response.content)
660 b980169d Antony Chazapis
    
661 b956618e Antony Chazapis
    if settings.TEST:
662 b956618e Antony Chazapis
        response['Date'] = format_date_time(time())
663 b956618e Antony Chazapis
664 b956618e Antony Chazapis
def render_fault(request, fault):
665 b956618e Antony Chazapis
    if settings.DEBUG or settings.TEST:
666 b956618e Antony Chazapis
        fault.details = format_exc(fault)
667 1993fea9 Antony Chazapis
    
668 b956618e Antony Chazapis
    request.serialization = 'text'
669 b956618e Antony Chazapis
    data = '\n'.join((fault.message, fault.details)) + '\n'
670 b956618e Antony Chazapis
    response = HttpResponse(data, status=fault.code)
671 b956618e Antony Chazapis
    update_response_headers(request, response)
672 b956618e Antony Chazapis
    return response
673 b956618e Antony Chazapis
674 b956618e Antony Chazapis
def request_serialization(request, format_allowed=False):
675 58a6c894 Antony Chazapis
    """Return the serialization format requested.
676 b956618e Antony Chazapis
    
677 b956618e Antony Chazapis
    Valid formats are 'text' and 'json', 'xml' if 'format_allowed' is True.
678 b956618e Antony Chazapis
    """
679 83dd59c5 Antony Chazapis
    
680 b956618e Antony Chazapis
    if not format_allowed:
681 b956618e Antony Chazapis
        return 'text'
682 b956618e Antony Chazapis
    
683 b956618e Antony Chazapis
    format = request.GET.get('format')
684 b956618e Antony Chazapis
    if format == 'json':
685 b956618e Antony Chazapis
        return 'json'
686 b956618e Antony Chazapis
    elif format == 'xml':
687 b956618e Antony Chazapis
        return 'xml'
688 b956618e Antony Chazapis
    
689 2938333b Antony Chazapis
#     for item in request.META.get('HTTP_ACCEPT', '').split(','):
690 2938333b Antony Chazapis
#         accept, sep, rest = item.strip().partition(';')
691 2938333b Antony Chazapis
#         if accept == 'application/json':
692 2938333b Antony Chazapis
#             return 'json'
693 2938333b Antony Chazapis
#         elif accept == 'application/xml' or accept == 'text/xml':
694 2938333b Antony Chazapis
#             return 'xml'
695 b956618e Antony Chazapis
    
696 b956618e Antony Chazapis
    return 'text'
697 b956618e Antony Chazapis
698 b956618e Antony Chazapis
def api_method(http_method=None, format_allowed=False):
699 58a6c894 Antony Chazapis
    """Decorator function for views that implement an API method."""
700 83dd59c5 Antony Chazapis
    
701 b956618e Antony Chazapis
    def decorator(func):
702 b956618e Antony Chazapis
        @wraps(func)
703 b956618e Antony Chazapis
        def wrapper(request, *args, **kwargs):
704 b956618e Antony Chazapis
            try:
705 b956618e Antony Chazapis
                if http_method and request.method != http_method:
706 b956618e Antony Chazapis
                    raise BadRequest('Method not allowed.')
707 2c22e4ac Antony Chazapis
                
708 b956618e Antony Chazapis
                # The args variable may contain up to (account, container, object).
709 b956618e Antony Chazapis
                if len(args) > 1 and len(args[1]) > 256:
710 b956618e Antony Chazapis
                    raise BadRequest('Container name too large.')
711 b956618e Antony Chazapis
                if len(args) > 2 and len(args[2]) > 1024:
712 b956618e Antony Chazapis
                    raise BadRequest('Object name too large.')
713 b956618e Antony Chazapis
                
714 b956618e Antony Chazapis
                # Fill in custom request variables.
715 b956618e Antony Chazapis
                request.serialization = request_serialization(request, format_allowed)
716 b956618e Antony Chazapis
                
717 b956618e Antony Chazapis
                response = func(request, *args, **kwargs)
718 b956618e Antony Chazapis
                update_response_headers(request, response)
719 b956618e Antony Chazapis
                return response
720 b956618e Antony Chazapis
            except Fault, fault:
721 b956618e Antony Chazapis
                return render_fault(request, fault)
722 b956618e Antony Chazapis
            except BaseException, e:
723 b956618e Antony Chazapis
                logger.exception('Unexpected error: %s' % e)
724 b956618e Antony Chazapis
                fault = ServiceUnavailable('Unexpected error')
725 b956618e Antony Chazapis
                return render_fault(request, fault)
726 b956618e Antony Chazapis
        return wrapper
727 b956618e Antony Chazapis
    return decorator