Statistics
| Branch: | Tag: | Revision:

root / pithos / api / util.py @ a7dff008

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