Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ 4a669c71

History | View | Annotate | Download (34.8 kB)

1 2e662088 Antony Chazapis
# Copyright 2011-2012 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 0d4ea090 Antony Chazapis
from binascii import hexlify, unhexlify
39 e11087c2 Antony Chazapis
from datetime import datetime, tzinfo, timedelta
40 9fefc052 Antony Chazapis
from urllib import quote, unquote
41 b956618e Antony Chazapis
42 b956618e Antony Chazapis
from django.conf import settings
43 b956618e Antony Chazapis
from django.http import HttpResponse
44 6b6b6c1e Antony Chazapis
from django.template.loader import render_to_string
45 1993fea9 Antony Chazapis
from django.utils import simplejson as json
46 22dab079 Antony Chazapis
from django.utils.http import http_date, parse_etags
47 fb064032 Antony Chazapis
from django.utils.encoding import smart_unicode, smart_str
48 817890f2 Antony Chazapis
from django.core.files.uploadhandler import FileUploadHandler
49 817890f2 Antony Chazapis
from django.core.files.uploadedfile import UploadedFile
50 b956618e Antony Chazapis
51 5a96180b Antony Chazapis
from pithos.lib.compat import parse_http_date_safe, parse_http_date
52 5a96180b Antony Chazapis
53 297513ba Antony Chazapis
from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound,
54 5df6c6d1 Antony Chazapis
                                Conflict, LengthRequired, PreconditionFailed, RequestEntityTooLarge,
55 08de868d Antony Chazapis
                                RangeNotSatisfiable, InternalServerError, NotImplemented)
56 bb4eafc6 Antony Chazapis
from pithos.api.short_url import encode_url
57 a7dff008 Antony Chazapis
from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION,
58 a7dff008 Antony Chazapis
                                    BACKEND_BLOCK_MODULE, BACKEND_BLOCK_PATH,
59 a7dff008 Antony Chazapis
                                    BACKEND_QUEUE_MODULE, BACKEND_QUEUE_CONNECTION,
60 a7dff008 Antony Chazapis
                                    BACKEND_QUOTA, BACKEND_VERSIONING)
61 39593b2b Giorgos Verigakis
from pithos.backends import connect_backend
62 5df6c6d1 Antony Chazapis
from pithos.backends.base import NotAllowedError, QuotaError
63 b956618e Antony Chazapis
64 b956618e Antony Chazapis
import logging
65 22dab079 Antony Chazapis
import re
66 cbfb6636 Sofia Papagiannaki
import hashlib
67 7bef5750 Antony Chazapis
import uuid
68 c48acbfd Sofia Papagiannaki
import decimal
69 8c793655 Antony Chazapis
70 b956618e Antony Chazapis
71 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
72 b956618e Antony Chazapis
73 b956618e Antony Chazapis
74 e11087c2 Antony Chazapis
class UTC(tzinfo):
75 e11087c2 Antony Chazapis
   def utcoffset(self, dt):
76 e11087c2 Antony Chazapis
       return timedelta(0)
77 e11087c2 Antony Chazapis
78 e11087c2 Antony Chazapis
   def tzname(self, dt):
79 e11087c2 Antony Chazapis
       return 'UTC'
80 e11087c2 Antony Chazapis
81 e11087c2 Antony Chazapis
   def dst(self, dt):
82 e11087c2 Antony Chazapis
       return timedelta(0)
83 e11087c2 Antony Chazapis
84 c48acbfd Sofia Papagiannaki
def json_encode_decimal(obj):
85 c48acbfd Sofia Papagiannaki
    if isinstance(obj, decimal.Decimal):
86 c48acbfd Sofia Papagiannaki
        return str(obj)
87 c48acbfd Sofia Papagiannaki
    raise TypeError(repr(obj) + " is not JSON serializable")
88 c48acbfd Sofia Papagiannaki
89 e11087c2 Antony Chazapis
def isoformat(d):
90 e11087c2 Antony Chazapis
   """Return an ISO8601 date string that includes a timezone."""
91 e11087c2 Antony Chazapis
92 e11087c2 Antony Chazapis
   return d.replace(tzinfo=UTC()).isoformat()
93 e11087c2 Antony Chazapis
94 804e8fe7 Antony Chazapis
def rename_meta_key(d, old, new):
95 804e8fe7 Antony Chazapis
    if old not in d:
96 804e8fe7 Antony Chazapis
        return
97 804e8fe7 Antony Chazapis
    d[new] = d[old]
98 804e8fe7 Antony Chazapis
    del(d[old])
99 804e8fe7 Antony Chazapis
100 02c0c3fa Antony Chazapis
def printable_header_dict(d):
101 b956618e Antony Chazapis
    """Format a meta dictionary for printing out json/xml.
102 b956618e Antony Chazapis
    
103 804e8fe7 Antony Chazapis
    Convert all keys to lower case and replace dashes with underscores.
104 804e8fe7 Antony Chazapis
    Format 'last_modified' timestamp.
105 b956618e Antony Chazapis
    """
106 83dd59c5 Antony Chazapis
    
107 371d907a Antony Chazapis
    if 'last_modified' in d and d['last_modified']:
108 690747fe Antony Chazapis
        d['last_modified'] = isoformat(datetime.fromtimestamp(d['last_modified']))
109 b956618e Antony Chazapis
    return dict([(k.lower().replace('-', '_'), v) for k, v in d.iteritems()])
110 b956618e Antony Chazapis
111 02c0c3fa Antony Chazapis
def format_header_key(k):
112 58a6c894 Antony Chazapis
    """Convert underscores to dashes and capitalize intra-dash strings."""
113 b956618e Antony Chazapis
    return '-'.join([x.capitalize() for x in k.replace('_', '-').split('-')])
114 b956618e Antony Chazapis
115 02c0c3fa Antony Chazapis
def get_header_prefix(request, prefix):
116 02c0c3fa Antony Chazapis
    """Get all prefix-* request headers in a dict. Reformat keys with format_header_key()."""
117 83dd59c5 Antony Chazapis
    
118 b956618e Antony Chazapis
    prefix = 'HTTP_' + prefix.upper().replace('-', '_')
119 3ab38c43 Antony Chazapis
    # TODO: Document or remove '~' replacing.
120 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)])
121 02c0c3fa Antony Chazapis
122 62bf8157 Antony Chazapis
def check_meta_headers(meta):
123 62bf8157 Antony Chazapis
    if len(meta) > 90:
124 62bf8157 Antony Chazapis
        raise BadRequest('Too many headers.')
125 62bf8157 Antony Chazapis
    for k, v in meta.iteritems():
126 62bf8157 Antony Chazapis
        if len(k) > 128:
127 62bf8157 Antony Chazapis
            raise BadRequest('Header name too large.')
128 62bf8157 Antony Chazapis
        if len(v) > 256:
129 62bf8157 Antony Chazapis
            raise BadRequest('Header value too large.')
130 62bf8157 Antony Chazapis
131 02c0c3fa Antony Chazapis
def get_account_headers(request):
132 02c0c3fa Antony Chazapis
    meta = get_header_prefix(request, 'X-Account-Meta-')
133 62bf8157 Antony Chazapis
    check_meta_headers(meta)
134 02c0c3fa Antony Chazapis
    groups = {}
135 02c0c3fa Antony Chazapis
    for k, v in get_header_prefix(request, 'X-Account-Group-').iteritems():
136 02c0c3fa Antony Chazapis
        n = k[16:].lower()
137 02c0c3fa Antony Chazapis
        if '-' in n or '_' in n:
138 02c0c3fa Antony Chazapis
            raise BadRequest('Bad characters in group name')
139 02c0c3fa Antony Chazapis
        groups[n] = v.replace(' ', '').split(',')
140 40d6b76d Antony Chazapis
        while '' in groups[n]:
141 02c0c3fa Antony Chazapis
            groups[n].remove('')
142 02c0c3fa Antony Chazapis
    return meta, groups
143 02c0c3fa Antony Chazapis
144 647a5f48 Antony Chazapis
def put_account_headers(response, meta, groups, policy):
145 f6c97079 Antony Chazapis
    if 'count' in meta:
146 f6c97079 Antony Chazapis
        response['X-Account-Container-Count'] = meta['count']
147 f6c97079 Antony Chazapis
    if 'bytes' in meta:
148 f6c97079 Antony Chazapis
        response['X-Account-Bytes-Used'] = meta['bytes']
149 d065f612 Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
150 b956618e Antony Chazapis
    for k in [x for x in meta.keys() if x.startswith('X-Account-Meta-')]:
151 e7b51248 Sofia Papagiannaki
        response[smart_str(k, strings_only=True)] = smart_str(meta[k], strings_only=True)
152 83dd59c5 Antony Chazapis
    if 'until_timestamp' in meta:
153 83dd59c5 Antony Chazapis
        response['X-Account-Until-Timestamp'] = http_date(int(meta['until_timestamp']))
154 02c0c3fa Antony Chazapis
    for k, v in groups.iteritems():
155 e7b51248 Sofia Papagiannaki
        k = smart_str(k, strings_only=True)
156 e7b51248 Sofia Papagiannaki
        k = format_header_key('X-Account-Group-' + k)
157 e7b51248 Sofia Papagiannaki
        v = smart_str(','.join(v), strings_only=True)
158 e7b51248 Sofia Papagiannaki
        response[k] = v
159 647a5f48 Antony Chazapis
    for k, v in policy.iteritems():
160 647a5f48 Antony Chazapis
        response[smart_str(format_header_key('X-Account-Policy-' + k), strings_only=True)] = smart_str(v, strings_only=True)
161 647a5f48 Antony Chazapis
162 02c0c3fa Antony Chazapis
def get_container_headers(request):
163 02c0c3fa Antony Chazapis
    meta = get_header_prefix(request, 'X-Container-Meta-')
164 62bf8157 Antony Chazapis
    check_meta_headers(meta)
165 3ab38c43 Antony Chazapis
    policy = dict([(k[19:].lower(), v.replace(' ', '')) for k, v in get_header_prefix(request, 'X-Container-Policy-').iteritems()])
166 3ab38c43 Antony Chazapis
    return meta, policy
167 b956618e Antony Chazapis
168 39593b2b Giorgos Verigakis
def put_container_headers(request, response, meta, policy):
169 f6c97079 Antony Chazapis
    if 'count' in meta:
170 f6c97079 Antony Chazapis
        response['X-Container-Object-Count'] = meta['count']
171 f6c97079 Antony Chazapis
    if 'bytes' in meta:
172 f6c97079 Antony Chazapis
        response['X-Container-Bytes-Used'] = meta['bytes']
173 58a6c894 Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
174 b956618e Antony Chazapis
    for k in [x for x in meta.keys() if x.startswith('X-Container-Meta-')]:
175 e7b51248 Sofia Papagiannaki
        response[smart_str(k, strings_only=True)] = smart_str(meta[k], strings_only=True)
176 e7b51248 Sofia Papagiannaki
    l = [smart_str(x, strings_only=True) for x in meta['object_meta'] if x.startswith('X-Object-Meta-')]
177 e7b51248 Sofia Papagiannaki
    response['X-Container-Object-Meta'] = ','.join([x[14:] for x in l])
178 39593b2b Giorgos Verigakis
    response['X-Container-Block-Size'] = request.backend.block_size
179 39593b2b Giorgos Verigakis
    response['X-Container-Block-Hash'] = request.backend.hash_algorithm
180 83dd59c5 Antony Chazapis
    if 'until_timestamp' in meta:
181 83dd59c5 Antony Chazapis
        response['X-Container-Until-Timestamp'] = http_date(int(meta['until_timestamp']))
182 3ab38c43 Antony Chazapis
    for k, v in policy.iteritems():
183 e7b51248 Sofia Papagiannaki
        response[smart_str(format_header_key('X-Container-Policy-' + k), strings_only=True)] = smart_str(v, strings_only=True)
184 b956618e Antony Chazapis
185 02c0c3fa Antony Chazapis
def get_object_headers(request):
186 66ce2ca5 Antony Chazapis
    content_type = request.META.get('CONTENT_TYPE', None)
187 02c0c3fa Antony Chazapis
    meta = get_header_prefix(request, 'X-Object-Meta-')
188 62bf8157 Antony Chazapis
    check_meta_headers(meta)
189 b956618e Antony Chazapis
    if request.META.get('HTTP_CONTENT_ENCODING'):
190 b956618e Antony Chazapis
        meta['Content-Encoding'] = request.META['HTTP_CONTENT_ENCODING']
191 22dab079 Antony Chazapis
    if request.META.get('HTTP_CONTENT_DISPOSITION'):
192 22dab079 Antony Chazapis
        meta['Content-Disposition'] = request.META['HTTP_CONTENT_DISPOSITION']
193 b956618e Antony Chazapis
    if request.META.get('HTTP_X_OBJECT_MANIFEST'):
194 b956618e Antony Chazapis
        meta['X-Object-Manifest'] = request.META['HTTP_X_OBJECT_MANIFEST']
195 66ce2ca5 Antony Chazapis
    return content_type, meta, get_sharing(request), get_public(request)
196 b956618e Antony Chazapis
197 3ab38c43 Antony Chazapis
def put_object_headers(response, meta, restricted=False):
198 33b4e4a6 Antony Chazapis
    response['ETag'] = meta['checksum']
199 b956618e Antony Chazapis
    response['Content-Length'] = meta['bytes']
200 66ce2ca5 Antony Chazapis
    response['Content-Type'] = meta.get('type', 'application/octet-stream')
201 b956618e Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
202 3ab38c43 Antony Chazapis
    if not restricted:
203 4a1c29ea Antony Chazapis
        response['X-Object-Hash'] = meta['hash']
204 37bee317 Antony Chazapis
        response['X-Object-UUID'] = meta['uuid']
205 72c3ba3f Antony Chazapis
        response['X-Object-Modified-By'] = smart_str(meta['modified_by'], strings_only=True)
206 7bef5750 Antony Chazapis
        response['X-Object-Version'] = meta['version']
207 104626e3 Antony Chazapis
        response['X-Object-Version-Timestamp'] = http_date(int(meta['version_timestamp']))
208 7bef5750 Antony Chazapis
        for k in [x for x in meta.keys() if x.startswith('X-Object-Meta-')]:
209 e7b51248 Sofia Papagiannaki
            response[smart_str(k, strings_only=True)] = smart_str(meta[k], strings_only=True)
210 067cf1fc Antony Chazapis
        for k in ('Content-Encoding', 'Content-Disposition', 'X-Object-Manifest',
211 067cf1fc Antony Chazapis
                  'X-Object-Sharing', 'X-Object-Shared-By', 'X-Object-Allowed-To',
212 067cf1fc Antony Chazapis
                  'X-Object-Public'):
213 7bef5750 Antony Chazapis
            if k in meta:
214 e7b51248 Sofia Papagiannaki
                response[k] = smart_str(meta[k], strings_only=True)
215 7bef5750 Antony Chazapis
    else:
216 c9af0703 Antony Chazapis
        for k in ('Content-Encoding', 'Content-Disposition'):
217 7bef5750 Antony Chazapis
            if k in meta:
218 e8d003e8 Antony Chazapis
                response[k] = smart_str(meta[k], strings_only=True)
219 b956618e Antony Chazapis
220 8cb45c13 Antony Chazapis
def update_manifest_meta(request, v_account, meta):
221 8cb45c13 Antony Chazapis
    """Update metadata if the object has an X-Object-Manifest."""
222 8cb45c13 Antony Chazapis
    
223 8cb45c13 Antony Chazapis
    if 'X-Object-Manifest' in meta:
224 4a1c29ea Antony Chazapis
        etag = ''
225 8cb45c13 Antony Chazapis
        bytes = 0
226 8cb45c13 Antony Chazapis
        try:
227 6d817842 Antony Chazapis
            src_container, src_name = split_container_object_string('/' + meta['X-Object-Manifest'])
228 61efb530 Antony Chazapis
            objects = request.backend.list_objects(request.user_uniq, v_account,
229 39593b2b Giorgos Verigakis
                                src_container, prefix=src_name, virtual=False)
230 8cb45c13 Antony Chazapis
            for x in objects:
231 61efb530 Antony Chazapis
                src_meta = request.backend.get_object_meta(request.user_uniq,
232 91817b22 chazapis
                                        v_account, src_container, x[0], 'pithos', x[1])
233 33b4e4a6 Antony Chazapis
                etag += src_meta['checksum']
234 8cb45c13 Antony Chazapis
                bytes += src_meta['bytes']
235 8cb45c13 Antony Chazapis
        except:
236 8cb45c13 Antony Chazapis
            # Ignore errors.
237 8cb45c13 Antony Chazapis
            return
238 8cb45c13 Antony Chazapis
        meta['bytes'] = bytes
239 8cb45c13 Antony Chazapis
        md5 = hashlib.md5()
240 4a1c29ea Antony Chazapis
        md5.update(etag)
241 33b4e4a6 Antony Chazapis
        meta['checksum'] = md5.hexdigest().lower()
242 8cb45c13 Antony Chazapis
243 067cf1fc Antony Chazapis
def update_sharing_meta(request, permissions, v_account, v_container, v_object, meta):
244 cca6c617 Antony Chazapis
    if permissions is None:
245 cca6c617 Antony Chazapis
        return
246 067cf1fc Antony Chazapis
    allowed, perm_path, perms = permissions
247 cca6c617 Antony Chazapis
    if len(perms) == 0:
248 cca6c617 Antony Chazapis
        return
249 3436eeb0 Antony Chazapis
    ret = []
250 cca6c617 Antony Chazapis
    r = ','.join(perms.get('read', []))
251 3436eeb0 Antony Chazapis
    if r:
252 3436eeb0 Antony Chazapis
        ret.append('read=' + r)
253 cca6c617 Antony Chazapis
    w = ','.join(perms.get('write', []))
254 3436eeb0 Antony Chazapis
    if w:
255 3436eeb0 Antony Chazapis
        ret.append('write=' + w)
256 cca6c617 Antony Chazapis
    meta['X-Object-Sharing'] = '; '.join(ret)
257 cca6c617 Antony Chazapis
    if '/'.join((v_account, v_container, v_object)) != perm_path:
258 cca6c617 Antony Chazapis
        meta['X-Object-Shared-By'] = perm_path
259 61efb530 Antony Chazapis
    if request.user_uniq != v_account:
260 067cf1fc Antony Chazapis
        meta['X-Object-Allowed-To'] = allowed
261 3436eeb0 Antony Chazapis
262 e0f916bb Antony Chazapis
def update_public_meta(public, meta):
263 e0f916bb Antony Chazapis
    if not public:
264 e0f916bb Antony Chazapis
        return
265 bb4eafc6 Antony Chazapis
    meta['X-Object-Public'] = '/public/' + encode_url(public)
266 e0f916bb Antony Chazapis
267 b956618e Antony Chazapis
def validate_modification_preconditions(request, meta):
268 58a6c894 Antony Chazapis
    """Check that the modified timestamp conforms with the preconditions set."""
269 83dd59c5 Antony Chazapis
    
270 22dab079 Antony Chazapis
    if 'modified' not in meta:
271 22dab079 Antony Chazapis
        return # TODO: Always return?
272 22dab079 Antony Chazapis
    
273 b956618e Antony Chazapis
    if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
274 b956618e Antony Chazapis
    if if_modified_since is not None:
275 b956618e Antony Chazapis
        if_modified_since = parse_http_date_safe(if_modified_since)
276 22dab079 Antony Chazapis
    if if_modified_since is not None and int(meta['modified']) <= if_modified_since:
277 3e5d2f38 Antony Chazapis
        raise NotModified('Resource has not been modified')
278 b956618e Antony Chazapis
    
279 b956618e Antony Chazapis
    if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')
280 b956618e Antony Chazapis
    if if_unmodified_since is not None:
281 b956618e Antony Chazapis
        if_unmodified_since = parse_http_date_safe(if_unmodified_since)
282 22dab079 Antony Chazapis
    if if_unmodified_since is not None and int(meta['modified']) > if_unmodified_since:
283 3e5d2f38 Antony Chazapis
        raise PreconditionFailed('Resource has been modified')
284 b956618e Antony Chazapis
285 22dab079 Antony Chazapis
def validate_matching_preconditions(request, meta):
286 58a6c894 Antony Chazapis
    """Check that the ETag conforms with the preconditions set."""
287 83dd59c5 Antony Chazapis
    
288 33b4e4a6 Antony Chazapis
    etag = meta['checksum']
289 33b4e4a6 Antony Chazapis
    if not etag:
290 33b4e4a6 Antony Chazapis
        etag = None
291 22dab079 Antony Chazapis
    
292 22dab079 Antony Chazapis
    if_match = request.META.get('HTTP_IF_MATCH')
293 a8326bef Antony Chazapis
    if if_match is not None:
294 4a1c29ea Antony Chazapis
        if etag is None:
295 a8326bef Antony Chazapis
            raise PreconditionFailed('Resource does not exist')
296 4a1c29ea Antony Chazapis
        if if_match != '*' and etag not in [x.lower() for x in parse_etags(if_match)]:
297 a8326bef Antony Chazapis
            raise PreconditionFailed('Resource ETag does not match')
298 22dab079 Antony Chazapis
    
299 22dab079 Antony Chazapis
    if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
300 22dab079 Antony Chazapis
    if if_none_match is not None:
301 a8326bef Antony Chazapis
        # TODO: If this passes, must ignore If-Modified-Since header.
302 4a1c29ea Antony Chazapis
        if etag is not None:
303 4a1c29ea Antony Chazapis
            if if_none_match == '*' or etag in [x.lower() for x in parse_etags(if_none_match)]:
304 a8326bef Antony Chazapis
                # TODO: Continue if an If-Modified-Since header is present.
305 a8326bef Antony Chazapis
                if request.method in ('HEAD', 'GET'):
306 a8326bef Antony Chazapis
                    raise NotModified('Resource ETag matches')
307 a8326bef Antony Chazapis
                raise PreconditionFailed('Resource exists or ETag matches')
308 22dab079 Antony Chazapis
309 83dd59c5 Antony Chazapis
def split_container_object_string(s):
310 6d817842 Antony Chazapis
    if not len(s) > 0 or s[0] != '/':
311 6d817842 Antony Chazapis
        raise ValueError
312 6d817842 Antony Chazapis
    s = s[1:]
313 8cb45c13 Antony Chazapis
    pos = s.find('/')
314 22d7b01e Antony Chazapis
    if pos == -1 or pos == len(s) - 1:
315 83dd59c5 Antony Chazapis
        raise ValueError
316 8cb45c13 Antony Chazapis
    return s[:pos], s[(pos + 1):]
317 83dd59c5 Antony Chazapis
318 79bb41b7 Antony Chazapis
def copy_or_move_object(request, src_account, src_container, src_name, dest_account, dest_container, dest_name, move=False):
319 58a6c894 Antony Chazapis
    """Copy or move an object."""
320 22dab079 Antony Chazapis
    
321 53cff70c Antony Chazapis
    if 'ignore_content_type' in request.GET and 'CONTENT_TYPE' in request.META:
322 53cff70c Antony Chazapis
        del(request.META['CONTENT_TYPE'])
323 66ce2ca5 Antony Chazapis
    content_type, meta, permissions, public = get_object_headers(request)
324 79bb41b7 Antony Chazapis
    src_version = request.META.get('HTTP_X_SOURCE_VERSION')
325 b956618e Antony Chazapis
    try:
326 b956618e Antony Chazapis
        if move:
327 61efb530 Antony Chazapis
            version_id = request.backend.move_object(request.user_uniq, src_account, src_container, src_name,
328 79bb41b7 Antony Chazapis
                                                        dest_account, dest_container, dest_name,
329 66ce2ca5 Antony Chazapis
                                                        content_type, 'pithos', meta, False, permissions)
330 b956618e Antony Chazapis
        else:
331 61efb530 Antony Chazapis
            version_id = request.backend.copy_object(request.user_uniq, src_account, src_container, src_name,
332 79bb41b7 Antony Chazapis
                                                        dest_account, dest_container, dest_name,
333 66ce2ca5 Antony Chazapis
                                                        content_type, 'pithos', meta, False, permissions, src_version)
334 cca6c617 Antony Chazapis
    except NotAllowedError:
335 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
336 a8326bef Antony Chazapis
    except (NameError, IndexError):
337 b956618e Antony Chazapis
        raise ItemNotFound('Container or object does not exist')
338 3436eeb0 Antony Chazapis
    except ValueError:
339 3436eeb0 Antony Chazapis
        raise BadRequest('Invalid sharing header')
340 5df6c6d1 Antony Chazapis
    except QuotaError:
341 5df6c6d1 Antony Chazapis
        raise RequestEntityTooLarge('Quota exceeded')
342 e0f916bb Antony Chazapis
    if public is not None:
343 e0f916bb Antony Chazapis
        try:
344 61efb530 Antony Chazapis
            request.backend.update_object_public(request.user_uniq, dest_account, dest_container, dest_name, public)
345 e0f916bb Antony Chazapis
        except NotAllowedError:
346 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
347 e0f916bb Antony Chazapis
        except NameError:
348 e0f916bb Antony Chazapis
            raise ItemNotFound('Object does not exist')
349 7dd293a0 Antony Chazapis
    return version_id
350 b956618e Antony Chazapis
351 1495b972 Antony Chazapis
def get_int_parameter(p):
352 83dd59c5 Antony Chazapis
    if p is not None:
353 58a6c894 Antony Chazapis
        try:
354 83dd59c5 Antony Chazapis
            p = int(p)
355 58a6c894 Antony Chazapis
        except ValueError:
356 58a6c894 Antony Chazapis
            return None
357 83dd59c5 Antony Chazapis
        if p < 0:
358 58a6c894 Antony Chazapis
            return None
359 83dd59c5 Antony Chazapis
    return p
360 58a6c894 Antony Chazapis
361 22dab079 Antony Chazapis
def get_content_length(request):
362 1495b972 Antony Chazapis
    content_length = get_int_parameter(request.META.get('CONTENT_LENGTH'))
363 1495b972 Antony Chazapis
    if content_length is None:
364 1495b972 Antony Chazapis
        raise LengthRequired('Missing or invalid Content-Length header')
365 22dab079 Antony Chazapis
    return content_length
366 22dab079 Antony Chazapis
367 22dab079 Antony Chazapis
def get_range(request, size):
368 58a6c894 Antony Chazapis
    """Parse a Range header from the request.
369 b956618e Antony Chazapis
    
370 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
371 22dab079 Antony Chazapis
    or a list of (offset, length) tuples - should be further checked.
372 b956618e Antony Chazapis
    """
373 83dd59c5 Antony Chazapis
    
374 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_RANGE', '').replace(' ', '')
375 22dab079 Antony Chazapis
    if not ranges.startswith('bytes='):
376 b956618e Antony Chazapis
        return None
377 b956618e Antony Chazapis
    
378 22dab079 Antony Chazapis
    ret = []
379 22dab079 Antony Chazapis
    for r in (x.strip() for x in ranges[6:].split(',')):
380 22dab079 Antony Chazapis
        p = re.compile('^(?P<offset>\d*)-(?P<upto>\d*)$')
381 22dab079 Antony Chazapis
        m = p.match(r)
382 22dab079 Antony Chazapis
        if not m:
383 22dab079 Antony Chazapis
            return None
384 22dab079 Antony Chazapis
        offset = m.group('offset')
385 22dab079 Antony Chazapis
        upto = m.group('upto')
386 22dab079 Antony Chazapis
        if offset == '' and upto == '':
387 b956618e Antony Chazapis
            return None
388 b956618e Antony Chazapis
        
389 22dab079 Antony Chazapis
        if offset != '':
390 22dab079 Antony Chazapis
            offset = int(offset)
391 22dab079 Antony Chazapis
            if upto != '':
392 b956618e Antony Chazapis
                upto = int(upto)
393 22dab079 Antony Chazapis
                if offset > upto:
394 22dab079 Antony Chazapis
                    return None
395 22dab079 Antony Chazapis
                ret.append((offset, upto - offset + 1))
396 22dab079 Antony Chazapis
            else:
397 22dab079 Antony Chazapis
                ret.append((offset, size - offset))
398 b956618e Antony Chazapis
        else:
399 22dab079 Antony Chazapis
            length = int(upto)
400 22dab079 Antony Chazapis
            ret.append((size - length, length))
401 22dab079 Antony Chazapis
    
402 22dab079 Antony Chazapis
    return ret
403 22dab079 Antony Chazapis
404 22dab079 Antony Chazapis
def get_content_range(request):
405 58a6c894 Antony Chazapis
    """Parse a Content-Range header from the request.
406 22dab079 Antony Chazapis
    
407 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
408 22dab079 Antony Chazapis
    or an (offset, length, total) tuple - check as length, total may be None.
409 22dab079 Antony Chazapis
    Returns (None, None, None) if the provided range is '*/*'.
410 22dab079 Antony Chazapis
    """
411 22dab079 Antony Chazapis
    
412 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_CONTENT_RANGE', '')
413 22dab079 Antony Chazapis
    if not ranges:
414 22dab079 Antony Chazapis
        return None
415 22dab079 Antony Chazapis
    
416 22dab079 Antony Chazapis
    p = re.compile('^bytes (?P<offset>\d+)-(?P<upto>\d*)/(?P<total>(\d+|\*))$')
417 22dab079 Antony Chazapis
    m = p.match(ranges)
418 22dab079 Antony Chazapis
    if not m:
419 22dab079 Antony Chazapis
        if ranges == 'bytes */*':
420 22dab079 Antony Chazapis
            return (None, None, None)
421 22dab079 Antony Chazapis
        return None
422 22dab079 Antony Chazapis
    offset = int(m.group('offset'))
423 22dab079 Antony Chazapis
    upto = m.group('upto')
424 22dab079 Antony Chazapis
    total = m.group('total')
425 22dab079 Antony Chazapis
    if upto != '':
426 22dab079 Antony Chazapis
        upto = int(upto)
427 b956618e Antony Chazapis
    else:
428 22dab079 Antony Chazapis
        upto = None
429 22dab079 Antony Chazapis
    if total != '*':
430 22dab079 Antony Chazapis
        total = int(total)
431 22dab079 Antony Chazapis
    else:
432 22dab079 Antony Chazapis
        total = None
433 70e526a0 Antony Chazapis
    if (upto is not None and offset > upto) or \
434 70e526a0 Antony Chazapis
        (total is not None and offset >= total) or \
435 70e526a0 Antony Chazapis
        (total is not None and upto is not None and upto >= total):
436 22dab079 Antony Chazapis
        return None
437 22dab079 Antony Chazapis
    
438 70e526a0 Antony Chazapis
    if upto is None:
439 22dab079 Antony Chazapis
        length = None
440 22dab079 Antony Chazapis
    else:
441 22dab079 Antony Chazapis
        length = upto - offset + 1
442 22dab079 Antony Chazapis
    return (offset, length, total)
443 b956618e Antony Chazapis
444 3436eeb0 Antony Chazapis
def get_sharing(request):
445 3436eeb0 Antony Chazapis
    """Parse an X-Object-Sharing header from the request.
446 3436eeb0 Antony Chazapis
    
447 3436eeb0 Antony Chazapis
    Raises BadRequest on error.
448 3436eeb0 Antony Chazapis
    """
449 3436eeb0 Antony Chazapis
    
450 3436eeb0 Antony Chazapis
    permissions = request.META.get('HTTP_X_OBJECT_SHARING')
451 cca6c617 Antony Chazapis
    if permissions is None:
452 3436eeb0 Antony Chazapis
        return None
453 3436eeb0 Antony Chazapis
    
454 0a7f1671 Antony Chazapis
    # TODO: Document or remove '~' replacing.
455 0a7f1671 Antony Chazapis
    permissions = permissions.replace('~', '')
456 0a7f1671 Antony Chazapis
    
457 3436eeb0 Antony Chazapis
    ret = {}
458 cca6c617 Antony Chazapis
    permissions = permissions.replace(' ', '')
459 cca6c617 Antony Chazapis
    if permissions == '':
460 cca6c617 Antony Chazapis
        return ret
461 cca6c617 Antony Chazapis
    for perm in (x for x in permissions.split(';')):
462 cca6c617 Antony Chazapis
        if perm.startswith('read='):
463 b0a2d1a6 Antony Chazapis
            ret['read'] = list(set([v.replace(' ','').lower() for v in perm[5:].split(',')]))
464 02c0c3fa Antony Chazapis
            if '' in ret['read']:
465 02c0c3fa Antony Chazapis
                ret['read'].remove('')
466 e8886082 Antony Chazapis
            if '*' in ret['read']:
467 e8886082 Antony Chazapis
                ret['read'] = ['*']
468 3436eeb0 Antony Chazapis
            if len(ret['read']) == 0:
469 3436eeb0 Antony Chazapis
                raise BadRequest('Bad X-Object-Sharing header value')
470 3436eeb0 Antony Chazapis
        elif perm.startswith('write='):
471 b0a2d1a6 Antony Chazapis
            ret['write'] = list(set([v.replace(' ','').lower() for v in perm[6:].split(',')]))
472 02c0c3fa Antony Chazapis
            if '' in ret['write']:
473 02c0c3fa Antony Chazapis
                ret['write'].remove('')
474 e8886082 Antony Chazapis
            if '*' in ret['write']:
475 e8886082 Antony Chazapis
                ret['write'] = ['*']
476 3436eeb0 Antony Chazapis
            if len(ret['write']) == 0:
477 3436eeb0 Antony Chazapis
                raise BadRequest('Bad X-Object-Sharing header value')
478 3436eeb0 Antony Chazapis
        else:
479 3436eeb0 Antony Chazapis
            raise BadRequest('Bad X-Object-Sharing header value')
480 21a8a6ff Antony Chazapis
    
481 21a8a6ff Antony Chazapis
    # Keep duplicates only in write list.
482 21a8a6ff Antony Chazapis
    dups = [x for x in ret.get('read', []) if x in ret.get('write', []) and x != '*']
483 21a8a6ff Antony Chazapis
    if dups:
484 21a8a6ff Antony Chazapis
        for x in dups:
485 21a8a6ff Antony Chazapis
            ret['read'].remove(x)
486 21a8a6ff Antony Chazapis
        if len(ret['read']) == 0:
487 21a8a6ff Antony Chazapis
            del(ret['read'])
488 21a8a6ff Antony Chazapis
    
489 3436eeb0 Antony Chazapis
    return ret
490 3436eeb0 Antony Chazapis
491 3ab38c43 Antony Chazapis
def get_public(request):
492 3ab38c43 Antony Chazapis
    """Parse an X-Object-Public header from the request.
493 3ab38c43 Antony Chazapis
    
494 3ab38c43 Antony Chazapis
    Raises BadRequest on error.
495 3ab38c43 Antony Chazapis
    """
496 3ab38c43 Antony Chazapis
    
497 3ab38c43 Antony Chazapis
    public = request.META.get('HTTP_X_OBJECT_PUBLIC')
498 3ab38c43 Antony Chazapis
    if public is None:
499 3ab38c43 Antony Chazapis
        return None
500 3ab38c43 Antony Chazapis
    
501 3ab38c43 Antony Chazapis
    public = public.replace(' ', '').lower()
502 3ab38c43 Antony Chazapis
    if public == 'true':
503 3ab38c43 Antony Chazapis
        return True
504 3ab38c43 Antony Chazapis
    elif public == 'false' or public == '':
505 3ab38c43 Antony Chazapis
        return False
506 3ab38c43 Antony Chazapis
    raise BadRequest('Bad X-Object-Public header value')
507 3ab38c43 Antony Chazapis
508 b956618e Antony Chazapis
def raw_input_socket(request):
509 58a6c894 Antony Chazapis
    """Return the socket for reading the rest of the request."""
510 83dd59c5 Antony Chazapis
    
511 b956618e Antony Chazapis
    server_software = request.META.get('SERVER_SOFTWARE')
512 fc1b2a75 Antony Chazapis
    if server_software and server_software.startswith('mod_python'):
513 b956618e Antony Chazapis
        return request._req
514 fc1b2a75 Antony Chazapis
    if 'wsgi.input' in request.environ:
515 fc1b2a75 Antony Chazapis
        return request.environ['wsgi.input']
516 08de868d Antony Chazapis
    raise NotImplemented('Unknown server software')
517 b956618e Antony Chazapis
518 07fea84b Antony Chazapis
MAX_UPLOAD_SIZE = 5 * (1024 * 1024 * 1024) # 5GB
519 b956618e Antony Chazapis
520 c032f34d Antony Chazapis
def socket_read_iterator(request, length=0, blocksize=4096):
521 58a6c894 Antony Chazapis
    """Return a maximum of blocksize data read from the socket in each iteration.
522 b956618e Antony Chazapis
    
523 22dab079 Antony Chazapis
    Read up to 'length'. If 'length' is negative, will attempt a chunked read.
524 b956618e Antony Chazapis
    The maximum ammount of data read is controlled by MAX_UPLOAD_SIZE.
525 b956618e Antony Chazapis
    """
526 83dd59c5 Antony Chazapis
    
527 c032f34d Antony Chazapis
    sock = raw_input_socket(request)
528 b956618e Antony Chazapis
    if length < 0: # Chunked transfers
529 c032f34d Antony Chazapis
        # Small version (server does the dechunking).
530 032dc768 Antony Chazapis
        if request.environ.get('mod_wsgi.input_chunked', None) or request.META['SERVER_SOFTWARE'].startswith('gunicorn'):
531 c032f34d Antony Chazapis
            while length < MAX_UPLOAD_SIZE:
532 c032f34d Antony Chazapis
                data = sock.read(blocksize)
533 c032f34d Antony Chazapis
                if data == '':
534 c032f34d Antony Chazapis
                    return
535 c032f34d Antony Chazapis
                yield data
536 c032f34d Antony Chazapis
            raise BadRequest('Maximum size is reached')
537 c032f34d Antony Chazapis
        
538 c032f34d Antony Chazapis
        # Long version (do the dechunking).
539 22dab079 Antony Chazapis
        data = ''
540 b956618e Antony Chazapis
        while length < MAX_UPLOAD_SIZE:
541 22dab079 Antony Chazapis
            # Get chunk size.
542 22dab079 Antony Chazapis
            if hasattr(sock, 'readline'):
543 22dab079 Antony Chazapis
                chunk_length = sock.readline()
544 22dab079 Antony Chazapis
            else:
545 22dab079 Antony Chazapis
                chunk_length = ''
546 22dab079 Antony Chazapis
                while chunk_length[-1:] != '\n':
547 22dab079 Antony Chazapis
                    chunk_length += sock.read(1)
548 22dab079 Antony Chazapis
                chunk_length.strip()
549 b956618e Antony Chazapis
            pos = chunk_length.find(';')
550 b956618e Antony Chazapis
            if pos >= 0:
551 b956618e Antony Chazapis
                chunk_length = chunk_length[:pos]
552 b956618e Antony Chazapis
            try:
553 b956618e Antony Chazapis
                chunk_length = int(chunk_length, 16)
554 b956618e Antony Chazapis
            except Exception, e:
555 b956618e Antony Chazapis
                raise BadRequest('Bad chunk size') # TODO: Change to something more appropriate.
556 22dab079 Antony Chazapis
            # Check if done.
557 b956618e Antony Chazapis
            if chunk_length == 0:
558 22dab079 Antony Chazapis
                if len(data) > 0:
559 22dab079 Antony Chazapis
                    yield data
560 b956618e Antony Chazapis
                return
561 22dab079 Antony Chazapis
            # Get the actual data.
562 b956618e Antony Chazapis
            while chunk_length > 0:
563 22dab079 Antony Chazapis
                chunk = sock.read(min(chunk_length, blocksize))
564 22dab079 Antony Chazapis
                chunk_length -= len(chunk)
565 623a0cf4 Sofia Papagiannaki
                if length > 0:
566 623a0cf4 Sofia Papagiannaki
                    length += len(chunk)
567 22dab079 Antony Chazapis
                data += chunk
568 22dab079 Antony Chazapis
                if len(data) >= blocksize:
569 22dab079 Antony Chazapis
                    ret = data[:blocksize]
570 22dab079 Antony Chazapis
                    data = data[blocksize:]
571 22dab079 Antony Chazapis
                    yield ret
572 22dab079 Antony Chazapis
            sock.read(2) # CRLF
573 32a437b1 Sofia Papagiannaki
        raise BadRequest('Maximum size is reached')
574 b956618e Antony Chazapis
    else:
575 b956618e Antony Chazapis
        if length > MAX_UPLOAD_SIZE:
576 32a437b1 Sofia Papagiannaki
            raise BadRequest('Maximum size is reached')
577 b956618e Antony Chazapis
        while length > 0:
578 b956618e Antony Chazapis
            data = sock.read(min(length, blocksize))
579 7b25e082 Antony Chazapis
            if not data:
580 7b25e082 Antony Chazapis
                raise BadRequest()
581 b956618e Antony Chazapis
            length -= len(data)
582 b956618e Antony Chazapis
            yield data
583 b956618e Antony Chazapis
584 817890f2 Antony Chazapis
class SaveToBackendHandler(FileUploadHandler):
585 817890f2 Antony Chazapis
    """Handle a file from an HTML form the django way."""
586 817890f2 Antony Chazapis
    
587 817890f2 Antony Chazapis
    def __init__(self, request=None):
588 817890f2 Antony Chazapis
        super(SaveToBackendHandler, self).__init__(request)
589 817890f2 Antony Chazapis
        self.backend = request.backend
590 817890f2 Antony Chazapis
    
591 817890f2 Antony Chazapis
    def put_data(self, length):
592 817890f2 Antony Chazapis
        if len(self.data) >= length:
593 817890f2 Antony Chazapis
            block = self.data[:length]
594 817890f2 Antony Chazapis
            self.file.hashmap.append(self.backend.put_block(block))
595 817890f2 Antony Chazapis
            self.md5.update(block)
596 817890f2 Antony Chazapis
            self.data = self.data[length:]
597 817890f2 Antony Chazapis
    
598 817890f2 Antony Chazapis
    def new_file(self, field_name, file_name, content_type, content_length, charset=None):
599 817890f2 Antony Chazapis
        self.md5 = hashlib.md5()        
600 817890f2 Antony Chazapis
        self.data = ''
601 817890f2 Antony Chazapis
        self.file = UploadedFile(name=file_name, content_type=content_type, charset=charset)
602 817890f2 Antony Chazapis
        self.file.size = 0
603 817890f2 Antony Chazapis
        self.file.hashmap = []
604 817890f2 Antony Chazapis
    
605 817890f2 Antony Chazapis
    def receive_data_chunk(self, raw_data, start):
606 817890f2 Antony Chazapis
        self.data += raw_data
607 817890f2 Antony Chazapis
        self.file.size += len(raw_data)
608 817890f2 Antony Chazapis
        self.put_data(self.request.backend.block_size)
609 817890f2 Antony Chazapis
        return None
610 817890f2 Antony Chazapis
    
611 817890f2 Antony Chazapis
    def file_complete(self, file_size):
612 817890f2 Antony Chazapis
        l = len(self.data)
613 817890f2 Antony Chazapis
        if l > 0:
614 817890f2 Antony Chazapis
            self.put_data(l)
615 817890f2 Antony Chazapis
        self.file.etag = self.md5.hexdigest().lower()
616 817890f2 Antony Chazapis
        return self.file
617 817890f2 Antony Chazapis
618 22dab079 Antony Chazapis
class ObjectWrapper(object):
619 58a6c894 Antony Chazapis
    """Return the object's data block-per-block in each iteration.
620 22dab079 Antony Chazapis
    
621 22dab079 Antony Chazapis
    Read from the object using the offset and length provided in each entry of the range list.
622 22dab079 Antony Chazapis
    """
623 22dab079 Antony Chazapis
    
624 39593b2b Giorgos Verigakis
    def __init__(self, backend, ranges, sizes, hashmaps, boundary):
625 39593b2b Giorgos Verigakis
        self.backend = backend
626 22dab079 Antony Chazapis
        self.ranges = ranges
627 8cb45c13 Antony Chazapis
        self.sizes = sizes
628 8cb45c13 Antony Chazapis
        self.hashmaps = hashmaps
629 22dab079 Antony Chazapis
        self.boundary = boundary
630 8cb45c13 Antony Chazapis
        self.size = sum(self.sizes)
631 22dab079 Antony Chazapis
        
632 8cb45c13 Antony Chazapis
        self.file_index = 0
633 8cb45c13 Antony Chazapis
        self.block_index = 0
634 8cb45c13 Antony Chazapis
        self.block_hash = -1
635 22dab079 Antony Chazapis
        self.block = ''
636 22dab079 Antony Chazapis
        
637 22dab079 Antony Chazapis
        self.range_index = -1
638 22dab079 Antony Chazapis
        self.offset, self.length = self.ranges[0]
639 22dab079 Antony Chazapis
    
640 22dab079 Antony Chazapis
    def __iter__(self):
641 22dab079 Antony Chazapis
        return self
642 22dab079 Antony Chazapis
    
643 22dab079 Antony Chazapis
    def part_iterator(self):
644 22dab079 Antony Chazapis
        if self.length > 0:
645 8cb45c13 Antony Chazapis
            # Get the file for the current offset.
646 8cb45c13 Antony Chazapis
            file_size = self.sizes[self.file_index]
647 8cb45c13 Antony Chazapis
            while self.offset >= file_size:
648 8cb45c13 Antony Chazapis
                self.offset -= file_size
649 8cb45c13 Antony Chazapis
                self.file_index += 1
650 8cb45c13 Antony Chazapis
                file_size = self.sizes[self.file_index]
651 8cb45c13 Antony Chazapis
            
652 8cb45c13 Antony Chazapis
            # Get the block for the current position.
653 39593b2b Giorgos Verigakis
            self.block_index = int(self.offset / self.backend.block_size)
654 8cb45c13 Antony Chazapis
            if self.block_hash != self.hashmaps[self.file_index][self.block_index]:
655 8cb45c13 Antony Chazapis
                self.block_hash = self.hashmaps[self.file_index][self.block_index]
656 22dab079 Antony Chazapis
                try:
657 39593b2b Giorgos Verigakis
                    self.block = self.backend.get_block(self.block_hash)
658 22dab079 Antony Chazapis
                except NameError:
659 22dab079 Antony Chazapis
                    raise ItemNotFound('Block does not exist')
660 8cb45c13 Antony Chazapis
            
661 22dab079 Antony Chazapis
            # Get the data from the block.
662 39593b2b Giorgos Verigakis
            bo = self.offset % self.backend.block_size
663 c9865fe1 Antony Chazapis
            bs = self.backend.block_size
664 c9865fe1 Antony Chazapis
            if self.block_index == len(self.hashmaps[self.file_index]) - 1:
665 c9865fe1 Antony Chazapis
                bs = self.sizes[self.file_index] % self.backend.block_size
666 c9865fe1 Antony Chazapis
            bl = min(self.length, bs - bo)
667 22dab079 Antony Chazapis
            data = self.block[bo:bo + bl]
668 22dab079 Antony Chazapis
            self.offset += bl
669 22dab079 Antony Chazapis
            self.length -= bl
670 22dab079 Antony Chazapis
            return data
671 22dab079 Antony Chazapis
        else:
672 22dab079 Antony Chazapis
            raise StopIteration
673 22dab079 Antony Chazapis
    
674 22dab079 Antony Chazapis
    def next(self):
675 22dab079 Antony Chazapis
        if len(self.ranges) == 1:
676 22dab079 Antony Chazapis
            return self.part_iterator()
677 22dab079 Antony Chazapis
        if self.range_index == len(self.ranges):
678 22dab079 Antony Chazapis
            raise StopIteration
679 22dab079 Antony Chazapis
        try:
680 22dab079 Antony Chazapis
            if self.range_index == -1:
681 22dab079 Antony Chazapis
                raise StopIteration
682 22dab079 Antony Chazapis
            return self.part_iterator()
683 22dab079 Antony Chazapis
        except StopIteration:
684 22dab079 Antony Chazapis
            self.range_index += 1
685 22dab079 Antony Chazapis
            out = []
686 22dab079 Antony Chazapis
            if self.range_index < len(self.ranges):
687 22dab079 Antony Chazapis
                # Part header.
688 22dab079 Antony Chazapis
                self.offset, self.length = self.ranges[self.range_index]
689 8cb45c13 Antony Chazapis
                self.file_index = 0
690 22dab079 Antony Chazapis
                if self.range_index > 0:
691 22dab079 Antony Chazapis
                    out.append('')
692 22dab079 Antony Chazapis
                out.append('--' + self.boundary)
693 22dab079 Antony Chazapis
                out.append('Content-Range: bytes %d-%d/%d' % (self.offset, self.offset + self.length - 1, self.size))
694 22dab079 Antony Chazapis
                out.append('Content-Transfer-Encoding: binary')
695 22dab079 Antony Chazapis
                out.append('')
696 22dab079 Antony Chazapis
                out.append('')
697 22dab079 Antony Chazapis
                return '\r\n'.join(out)
698 22dab079 Antony Chazapis
            else:
699 22dab079 Antony Chazapis
                # Footer.
700 22dab079 Antony Chazapis
                out.append('')
701 22dab079 Antony Chazapis
                out.append('--' + self.boundary + '--')
702 22dab079 Antony Chazapis
                out.append('')
703 22dab079 Antony Chazapis
                return '\r\n'.join(out)
704 22dab079 Antony Chazapis
705 8cb45c13 Antony Chazapis
def object_data_response(request, sizes, hashmaps, meta, public=False):
706 7bef5750 Antony Chazapis
    """Get the HttpResponse object for replying with the object's data."""
707 7bef5750 Antony Chazapis
    
708 7bef5750 Antony Chazapis
    # Range handling.
709 8cb45c13 Antony Chazapis
    size = sum(sizes)
710 7bef5750 Antony Chazapis
    ranges = get_range(request, size)
711 7bef5750 Antony Chazapis
    if ranges is None:
712 7bef5750 Antony Chazapis
        ranges = [(0, size)]
713 7bef5750 Antony Chazapis
        ret = 200
714 7bef5750 Antony Chazapis
    else:
715 7bef5750 Antony Chazapis
        check = [True for offset, length in ranges if
716 7bef5750 Antony Chazapis
                    length <= 0 or length > size or
717 7bef5750 Antony Chazapis
                    offset < 0 or offset >= size or
718 7bef5750 Antony Chazapis
                    offset + length > size]
719 7bef5750 Antony Chazapis
        if len(check) > 0:
720 6d1e6dce Sofia Papagiannaki
            raise RangeNotSatisfiable('Requested range exceeds object limits')
721 7bef5750 Antony Chazapis
        ret = 206
722 15d465b8 Antony Chazapis
        if_range = request.META.get('HTTP_IF_RANGE')
723 15d465b8 Antony Chazapis
        if if_range:
724 6d1e6dce Sofia Papagiannaki
            try:
725 15d465b8 Antony Chazapis
                # Modification time has passed instead.
726 6d1e6dce Sofia Papagiannaki
                last_modified = parse_http_date(if_range)
727 6d1e6dce Sofia Papagiannaki
                if last_modified != meta['modified']:
728 6d1e6dce Sofia Papagiannaki
                    ranges = [(0, size)]
729 6d1e6dce Sofia Papagiannaki
                    ret = 200
730 6d1e6dce Sofia Papagiannaki
            except ValueError:
731 33b4e4a6 Antony Chazapis
                if if_range != meta['checksum']:
732 6d1e6dce Sofia Papagiannaki
                    ranges = [(0, size)]
733 6d1e6dce Sofia Papagiannaki
                    ret = 200
734 7bef5750 Antony Chazapis
    
735 7bef5750 Antony Chazapis
    if ret == 206 and len(ranges) > 1:
736 7bef5750 Antony Chazapis
        boundary = uuid.uuid4().hex
737 7bef5750 Antony Chazapis
    else:
738 7bef5750 Antony Chazapis
        boundary = ''
739 39593b2b Giorgos Verigakis
    wrapper = ObjectWrapper(request.backend, ranges, sizes, hashmaps, boundary)
740 7bef5750 Antony Chazapis
    response = HttpResponse(wrapper, status=ret)
741 02c0c3fa Antony Chazapis
    put_object_headers(response, meta, public)
742 7bef5750 Antony Chazapis
    if ret == 206:
743 7bef5750 Antony Chazapis
        if len(ranges) == 1:
744 7bef5750 Antony Chazapis
            offset, length = ranges[0]
745 7bef5750 Antony Chazapis
            response['Content-Length'] = length # Update with the correct length.
746 7bef5750 Antony Chazapis
            response['Content-Range'] = 'bytes %d-%d/%d' % (offset, offset + length - 1, size)
747 7bef5750 Antony Chazapis
        else:
748 7bef5750 Antony Chazapis
            del(response['Content-Length'])
749 7bef5750 Antony Chazapis
            response['Content-Type'] = 'multipart/byteranges; boundary=%s' % (boundary,)
750 7bef5750 Antony Chazapis
    return response
751 7bef5750 Antony Chazapis
752 39593b2b Giorgos Verigakis
def put_object_block(request, hashmap, data, offset):
753 cb146cf9 Antony Chazapis
    """Put one block of data at the given offset."""
754 cb146cf9 Antony Chazapis
    
755 39593b2b Giorgos Verigakis
    bi = int(offset / request.backend.block_size)
756 39593b2b Giorgos Verigakis
    bo = offset % request.backend.block_size
757 39593b2b Giorgos Verigakis
    bl = min(len(data), request.backend.block_size - bo)
758 cb146cf9 Antony Chazapis
    if bi < len(hashmap):
759 39593b2b Giorgos Verigakis
        hashmap[bi] = request.backend.update_block(hashmap[bi], data[:bl], bo)
760 cb146cf9 Antony Chazapis
    else:
761 39593b2b Giorgos Verigakis
        hashmap.append(request.backend.put_block(('\x00' * bo) + data[:bl]))
762 cb146cf9 Antony Chazapis
    return bl # Return ammount of data written.
763 cb146cf9 Antony Chazapis
764 cddcf432 chazapis
def hashmap_md5(request, hashmap, size):
765 cddcf432 chazapis
    """Produce the MD5 sum from the data in the hashmap."""
766 cddcf432 chazapis
    
767 cddcf432 chazapis
    # TODO: Search backend for the MD5 of another object with the same hashmap and size...
768 cddcf432 chazapis
    md5 = hashlib.md5()
769 cddcf432 chazapis
    bs = request.backend.block_size
770 cddcf432 chazapis
    for bi, hash in enumerate(hashmap):
771 c9865fe1 Antony Chazapis
        data = request.backend.get_block(hash) # Blocks come in padded.
772 cddcf432 chazapis
        if bi == len(hashmap) - 1:
773 c9865fe1 Antony Chazapis
            data = data[:size % bs]
774 c9865fe1 Antony Chazapis
        md5.update(data)
775 cddcf432 chazapis
    return md5.hexdigest().lower()
776 49133392 Antony Chazapis
777 af7bb62f Antony Chazapis
def simple_list_response(request, l):
778 6b6b6c1e Antony Chazapis
    if request.serialization == 'text':
779 6b6b6c1e Antony Chazapis
        return '\n'.join(l) + '\n'
780 6b6b6c1e Antony Chazapis
    if request.serialization == 'xml':
781 af7bb62f Antony Chazapis
        return render_to_string('items.xml', {'items': l})
782 6b6b6c1e Antony Chazapis
    if request.serialization == 'json':
783 6b6b6c1e Antony Chazapis
        return json.dumps(l)
784 6b6b6c1e Antony Chazapis
785 228de81b Antony Chazapis
def get_backend():
786 a7dff008 Antony Chazapis
    backend = connect_backend(db_module=BACKEND_DB_MODULE,
787 a7dff008 Antony Chazapis
                              db_connection=BACKEND_DB_CONNECTION,
788 a7dff008 Antony Chazapis
                              block_module=BACKEND_BLOCK_MODULE,
789 a7dff008 Antony Chazapis
                              block_path=BACKEND_BLOCK_PATH,
790 a7dff008 Antony Chazapis
                              queue_module=BACKEND_QUEUE_MODULE,
791 a7dff008 Antony Chazapis
                              queue_connection=BACKEND_QUEUE_CONNECTION)
792 a7dff008 Antony Chazapis
    backend.default_policy['quota'] = BACKEND_QUOTA
793 a7dff008 Antony Chazapis
    backend.default_policy['versioning'] = BACKEND_VERSIONING
794 228de81b Antony Chazapis
    return backend
795 228de81b Antony Chazapis
796 9fefc052 Antony Chazapis
def update_request_headers(request):
797 9fefc052 Antony Chazapis
    # Handle URL-encoded keys and values.
798 88283e9e Antony Chazapis
    meta = dict([(k, v) for k, v in request.META.iteritems() if k.startswith('HTTP_')])
799 88283e9e Antony Chazapis
    for k, v in meta.iteritems():
800 88283e9e Antony Chazapis
        try:
801 88283e9e Antony Chazapis
            k.decode('ascii')
802 88283e9e Antony Chazapis
            v.decode('ascii')
803 88283e9e Antony Chazapis
        except UnicodeDecodeError:
804 88283e9e Antony Chazapis
            raise BadRequest('Bad character in headers.')
805 88283e9e Antony Chazapis
        if '%' in k or '%' in v:
806 88283e9e Antony Chazapis
            del(request.META[k])
807 88283e9e Antony Chazapis
            request.META[unquote(k)] = smart_unicode(unquote(v), strings_only=True)
808 9fefc052 Antony Chazapis
809 b956618e Antony Chazapis
def update_response_headers(request, response):
810 b956618e Antony Chazapis
    if request.serialization == 'xml':
811 b956618e Antony Chazapis
        response['Content-Type'] = 'application/xml; charset=UTF-8'
812 b956618e Antony Chazapis
    elif request.serialization == 'json':
813 b956618e Antony Chazapis
        response['Content-Type'] = 'application/json; charset=UTF-8'
814 22dab079 Antony Chazapis
    elif not response['Content-Type']:
815 b956618e Antony Chazapis
        response['Content-Type'] = 'text/plain; charset=UTF-8'
816 b980169d Antony Chazapis
    
817 9fefc052 Antony Chazapis
    if (not response.has_header('Content-Length') and
818 9fefc052 Antony Chazapis
        not (response.has_header('Content-Type') and
819 9fefc052 Antony Chazapis
             response['Content-Type'].startswith('multipart/byteranges'))):
820 b980169d Antony Chazapis
        response['Content-Length'] = len(response.content)
821 b980169d Antony Chazapis
    
822 9fefc052 Antony Chazapis
    # URL-encode unicode in headers.
823 9fefc052 Antony Chazapis
    meta = response.items()
824 9fefc052 Antony Chazapis
    for k, v in meta:
825 88283e9e Antony Chazapis
        if (k.startswith('X-Account-') or k.startswith('X-Container-') or
826 88283e9e Antony Chazapis
            k.startswith('X-Object-') or k.startswith('Content-')):
827 9fefc052 Antony Chazapis
            del(response[k])
828 88283e9e Antony Chazapis
            response[quote(k)] = quote(v, safe='/=,:@; ')
829 b956618e Antony Chazapis
830 b956618e Antony Chazapis
def render_fault(request, fault):
831 a7dff008 Antony Chazapis
    if isinstance(fault, InternalServerError) and settings.DEBUG:
832 b956618e Antony Chazapis
        fault.details = format_exc(fault)
833 1993fea9 Antony Chazapis
    
834 b956618e Antony Chazapis
    request.serialization = 'text'
835 08de868d Antony Chazapis
    data = fault.message + '\n'
836 08de868d Antony Chazapis
    if fault.details:
837 08de868d Antony Chazapis
        data += '\n' + fault.details
838 b956618e Antony Chazapis
    response = HttpResponse(data, status=fault.code)
839 b956618e Antony Chazapis
    update_response_headers(request, response)
840 b956618e Antony Chazapis
    return response
841 b956618e Antony Chazapis
842 b956618e Antony Chazapis
def request_serialization(request, format_allowed=False):
843 58a6c894 Antony Chazapis
    """Return the serialization format requested.
844 b956618e Antony Chazapis
    
845 b956618e Antony Chazapis
    Valid formats are 'text' and 'json', 'xml' if 'format_allowed' is True.
846 b956618e Antony Chazapis
    """
847 83dd59c5 Antony Chazapis
    
848 b956618e Antony Chazapis
    if not format_allowed:
849 b956618e Antony Chazapis
        return 'text'
850 b956618e Antony Chazapis
    
851 b956618e Antony Chazapis
    format = request.GET.get('format')
852 b956618e Antony Chazapis
    if format == 'json':
853 b956618e Antony Chazapis
        return 'json'
854 b956618e Antony Chazapis
    elif format == 'xml':
855 b956618e Antony Chazapis
        return 'xml'
856 b956618e Antony Chazapis
    
857 f9f15f92 Antony Chazapis
    for item in request.META.get('HTTP_ACCEPT', '').split(','):
858 f9f15f92 Antony Chazapis
        accept, sep, rest = item.strip().partition(';')
859 f9f15f92 Antony Chazapis
        if accept == 'application/json':
860 f9f15f92 Antony Chazapis
            return 'json'
861 f9f15f92 Antony Chazapis
        elif accept == 'application/xml' or accept == 'text/xml':
862 f9f15f92 Antony Chazapis
            return 'xml'
863 b956618e Antony Chazapis
    
864 b956618e Antony Chazapis
    return 'text'
865 b956618e Antony Chazapis
866 297513ba Antony Chazapis
def api_method(http_method=None, format_allowed=False, user_required=True):
867 58a6c894 Antony Chazapis
    """Decorator function for views that implement an API method."""
868 83dd59c5 Antony Chazapis
    
869 b956618e Antony Chazapis
    def decorator(func):
870 b956618e Antony Chazapis
        @wraps(func)
871 b956618e Antony Chazapis
        def wrapper(request, *args, **kwargs):
872 b956618e Antony Chazapis
            try:
873 b956618e Antony Chazapis
                if http_method and request.method != http_method:
874 b956618e Antony Chazapis
                    raise BadRequest('Method not allowed.')
875 297513ba Antony Chazapis
                if user_required and getattr(request, 'user', None) is None:
876 297513ba Antony Chazapis
                    raise Unauthorized('Access denied')
877 2c22e4ac Antony Chazapis
                
878 b956618e Antony Chazapis
                # The args variable may contain up to (account, container, object).
879 b956618e Antony Chazapis
                if len(args) > 1 and len(args[1]) > 256:
880 b956618e Antony Chazapis
                    raise BadRequest('Container name too large.')
881 b956618e Antony Chazapis
                if len(args) > 2 and len(args[2]) > 1024:
882 b956618e Antony Chazapis
                    raise BadRequest('Object name too large.')
883 b956618e Antony Chazapis
                
884 9fefc052 Antony Chazapis
                # Format and check headers.
885 9fefc052 Antony Chazapis
                update_request_headers(request)
886 9fefc052 Antony Chazapis
                
887 b956618e Antony Chazapis
                # Fill in custom request variables.
888 b956618e Antony Chazapis
                request.serialization = request_serialization(request, format_allowed)
889 228de81b Antony Chazapis
                request.backend = get_backend()
890 07813fd6 Sofia Papagiannaki
                
891 b956618e Antony Chazapis
                response = func(request, *args, **kwargs)
892 b956618e Antony Chazapis
                update_response_headers(request, response)
893 b956618e Antony Chazapis
                return response
894 b956618e Antony Chazapis
            except Fault, fault:
895 b956618e Antony Chazapis
                return render_fault(request, fault)
896 b956618e Antony Chazapis
            except BaseException, e:
897 b956618e Antony Chazapis
                logger.exception('Unexpected error: %s' % e)
898 08de868d Antony Chazapis
                fault = InternalServerError('Unexpected error')
899 b956618e Antony Chazapis
                return render_fault(request, fault)
900 7b25e082 Antony Chazapis
            finally:
901 297513ba Antony Chazapis
                if getattr(request, 'backend', None) is not None:
902 d14fe290 Antony Chazapis
                    request.backend.close()
903 b956618e Antony Chazapis
        return wrapper
904 b956618e Antony Chazapis
    return decorator