Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ 1427ce62

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