Statistics
| Branch: | Tag: | Revision:

root / pithos / api / util.py @ 0a7f1671

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