Statistics
| Branch: | Tag: | Revision:

root / pithos / api / util.py @ 5635f9ef

History | View | Annotate | Download (19.1 kB)

1 5635f9ef Antony Chazapis
# Copyright 2011 GRNET S.A. All rights reserved.
2 5635f9ef Antony Chazapis
# 
3 5635f9ef Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 5635f9ef Antony Chazapis
# without modification, are permitted provided that the following
5 5635f9ef Antony Chazapis
# conditions are met:
6 5635f9ef Antony Chazapis
# 
7 5635f9ef Antony Chazapis
#   1. Redistributions of source code must retain the above
8 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
9 5635f9ef Antony Chazapis
#      disclaimer.
10 5635f9ef Antony Chazapis
# 
11 5635f9ef Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
13 5635f9ef Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 5635f9ef Antony Chazapis
#      provided with the distribution.
15 5635f9ef Antony Chazapis
# 
16 5635f9ef Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 5635f9ef Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 5635f9ef Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 5635f9ef Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 5635f9ef Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 5635f9ef Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 5635f9ef Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 5635f9ef Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 5635f9ef Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 5635f9ef Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 5635f9ef Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 5635f9ef Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 5635f9ef Antony Chazapis
# 
29 5635f9ef Antony Chazapis
# The views and conclusions contained in the software and
30 5635f9ef Antony Chazapis
# documentation are those of the authors and should not be
31 5635f9ef Antony Chazapis
# interpreted as representing official policies, either expressed
32 5635f9ef Antony Chazapis
# or implied, of GRNET S.A.
33 5635f9ef Antony Chazapis
34 b956618e Antony Chazapis
from functools import wraps
35 b956618e Antony Chazapis
from time import time
36 b956618e Antony Chazapis
from traceback import format_exc
37 b956618e Antony Chazapis
from wsgiref.handlers import format_date_time
38 b956618e Antony Chazapis
39 b956618e Antony Chazapis
from django.conf import settings
40 b956618e Antony Chazapis
from django.http import HttpResponse
41 22dab079 Antony Chazapis
from django.utils.http import http_date, parse_etags
42 b956618e Antony Chazapis
43 b956618e Antony Chazapis
from pithos.api.compat import parse_http_date_safe
44 22dab079 Antony Chazapis
from pithos.api.faults import (Fault, NotModified, BadRequest, ItemNotFound, LengthRequired,
45 22dab079 Antony Chazapis
                                PreconditionFailed, ServiceUnavailable)
46 b956618e Antony Chazapis
from pithos.backends import backend
47 b956618e Antony Chazapis
48 b956618e Antony Chazapis
import datetime
49 b956618e Antony Chazapis
import logging
50 22dab079 Antony Chazapis
import re
51 b956618e Antony Chazapis
52 b956618e Antony Chazapis
53 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
54 b956618e Antony Chazapis
55 b956618e Antony Chazapis
56 b956618e Antony Chazapis
def printable_meta_dict(d):
57 b956618e Antony Chazapis
    """Format a meta dictionary for printing out json/xml.
58 b956618e Antony Chazapis
    
59 b956618e Antony Chazapis
    Convert all keys to lower case and replace dashes to underscores.
60 b956618e Antony Chazapis
    Change 'modified' key from backend to 'last_modified' and format date.
61 b956618e Antony Chazapis
    """
62 b956618e Antony Chazapis
    if 'modified' in d:
63 b956618e Antony Chazapis
        d['last_modified'] = datetime.datetime.fromtimestamp(int(d['modified'])).isoformat()
64 b956618e Antony Chazapis
        del(d['modified'])
65 b956618e Antony Chazapis
    return dict([(k.lower().replace('-', '_'), v) for k, v in d.iteritems()])
66 b956618e Antony Chazapis
67 b956618e Antony Chazapis
def format_meta_key(k):
68 b956618e Antony Chazapis
    """Convert underscores to dashes and capitalize intra-dash strings"""
69 b956618e Antony Chazapis
    return '-'.join([x.capitalize() for x in k.replace('_', '-').split('-')])
70 b956618e Antony Chazapis
71 b956618e Antony Chazapis
def get_meta_prefix(request, prefix):
72 b956618e Antony Chazapis
    """Get all prefix-* request headers in a dict. Reformat keys with format_meta_key()"""
73 b956618e Antony Chazapis
    prefix = 'HTTP_' + prefix.upper().replace('-', '_')
74 b956618e Antony Chazapis
    return dict([(format_meta_key(k[5:]), v) for k, v in request.META.iteritems() if k.startswith(prefix)])
75 b956618e Antony Chazapis
76 b956618e Antony Chazapis
def get_account_meta(request):
77 b956618e Antony Chazapis
    """Get metadata from an account request"""
78 b956618e Antony Chazapis
    meta = get_meta_prefix(request, 'X-Account-Meta-')    
79 b956618e Antony Chazapis
    return meta
80 b956618e Antony Chazapis
81 b956618e Antony Chazapis
def put_account_meta(response, meta):
82 b956618e Antony Chazapis
    """Put metadata in an account response"""
83 b956618e Antony Chazapis
    response['X-Account-Container-Count'] = meta['count']
84 b956618e Antony Chazapis
    response['X-Account-Bytes-Used'] = meta['bytes']
85 b956618e Antony Chazapis
    if 'modified' in meta:
86 b956618e Antony Chazapis
        response['Last-Modified'] = http_date(int(meta['modified']))
87 b956618e Antony Chazapis
    for k in [x for x in meta.keys() if x.startswith('X-Account-Meta-')]:
88 b956618e Antony Chazapis
        response[k.encode('utf-8')] = meta[k].encode('utf-8')
89 b956618e Antony Chazapis
90 b956618e Antony Chazapis
def get_container_meta(request):
91 b956618e Antony Chazapis
    """Get metadata from a container request"""
92 b956618e Antony Chazapis
    meta = get_meta_prefix(request, 'X-Container-Meta-')
93 b956618e Antony Chazapis
    return meta
94 b956618e Antony Chazapis
95 b956618e Antony Chazapis
def put_container_meta(response, meta):
96 b956618e Antony Chazapis
    """Put metadata in a container response"""
97 b956618e Antony Chazapis
    response['X-Container-Object-Count'] = meta['count']
98 b956618e Antony Chazapis
    response['X-Container-Bytes-Used'] = meta['bytes']
99 b956618e Antony Chazapis
    if 'modified' in meta:
100 b956618e Antony Chazapis
        response['Last-Modified'] = http_date(int(meta['modified']))
101 b956618e Antony Chazapis
    for k in [x for x in meta.keys() if x.startswith('X-Container-Meta-')]:
102 b956618e Antony Chazapis
        response[k.encode('utf-8')] = meta[k].encode('utf-8')
103 22dab079 Antony Chazapis
    response['X-Container-Object-Meta'] = [x[14:] for x in meta['object_meta'] if x.startswith('X-Object-Meta-')]
104 b956618e Antony Chazapis
105 b956618e Antony Chazapis
def get_object_meta(request):
106 b956618e Antony Chazapis
    """Get metadata from an object request"""
107 b956618e Antony Chazapis
    meta = get_meta_prefix(request, 'X-Object-Meta-')
108 b956618e Antony Chazapis
    if request.META.get('CONTENT_TYPE'):
109 b956618e Antony Chazapis
        meta['Content-Type'] = request.META['CONTENT_TYPE']
110 b956618e Antony Chazapis
    if request.META.get('HTTP_CONTENT_ENCODING'):
111 b956618e Antony Chazapis
        meta['Content-Encoding'] = request.META['HTTP_CONTENT_ENCODING']
112 22dab079 Antony Chazapis
    if request.META.get('HTTP_CONTENT_DISPOSITION'):
113 22dab079 Antony Chazapis
        meta['Content-Disposition'] = request.META['HTTP_CONTENT_DISPOSITION']
114 b956618e Antony Chazapis
    if request.META.get('HTTP_X_OBJECT_MANIFEST'):
115 b956618e Antony Chazapis
        meta['X-Object-Manifest'] = request.META['HTTP_X_OBJECT_MANIFEST']
116 b956618e Antony Chazapis
    return meta
117 b956618e Antony Chazapis
118 b956618e Antony Chazapis
def put_object_meta(response, meta):
119 b956618e Antony Chazapis
    """Put metadata in an object response"""
120 b956618e Antony Chazapis
    response['ETag'] = meta['hash']
121 b956618e Antony Chazapis
    response['Content-Length'] = meta['bytes']
122 b956618e Antony Chazapis
    response['Content-Type'] = meta.get('Content-Type', 'application/octet-stream')
123 b956618e Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
124 b956618e Antony Chazapis
    for k in [x for x in meta.keys() if x.startswith('X-Object-Meta-')]:
125 b956618e Antony Chazapis
        response[k.encode('utf-8')] = meta[k].encode('utf-8')
126 22dab079 Antony Chazapis
    for k in ('Content-Encoding', 'Content-Disposition', 'X-Object-Manifest'):
127 b956618e Antony Chazapis
        if k in meta:
128 b956618e Antony Chazapis
            response[k] = meta[k]
129 b956618e Antony Chazapis
130 b956618e Antony Chazapis
def validate_modification_preconditions(request, meta):
131 b956618e Antony Chazapis
    """Check that the modified timestamp conforms with the preconditions set"""
132 22dab079 Antony Chazapis
    if 'modified' not in meta:
133 22dab079 Antony Chazapis
        return # TODO: Always return?
134 22dab079 Antony Chazapis
    
135 b956618e Antony Chazapis
    if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
136 b956618e Antony Chazapis
    if if_modified_since is not None:
137 b956618e Antony Chazapis
        if_modified_since = parse_http_date_safe(if_modified_since)
138 22dab079 Antony Chazapis
    if if_modified_since is not None and int(meta['modified']) <= if_modified_since:
139 b956618e Antony Chazapis
        raise NotModified('Object has not been modified')
140 b956618e Antony Chazapis
    
141 b956618e Antony Chazapis
    if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')
142 b956618e Antony Chazapis
    if if_unmodified_since is not None:
143 b956618e Antony Chazapis
        if_unmodified_since = parse_http_date_safe(if_unmodified_since)
144 22dab079 Antony Chazapis
    if if_unmodified_since is not None and int(meta['modified']) > if_unmodified_since:
145 b956618e Antony Chazapis
        raise PreconditionFailed('Object has been modified')
146 b956618e Antony Chazapis
147 22dab079 Antony Chazapis
def validate_matching_preconditions(request, meta):
148 22dab079 Antony Chazapis
    """Check that the ETag conforms with the preconditions set"""
149 22dab079 Antony Chazapis
    if 'hash' not in meta:
150 22dab079 Antony Chazapis
        return # TODO: Always return?
151 22dab079 Antony Chazapis
    
152 22dab079 Antony Chazapis
    if_match = request.META.get('HTTP_IF_MATCH')
153 22dab079 Antony Chazapis
    if if_match is not None and if_match != '*':
154 22dab079 Antony Chazapis
        if meta['hash'] not in [x.lower() for x in parse_etags(if_match)]:
155 22dab079 Antony Chazapis
            raise PreconditionFailed('Object Etag does not match')
156 22dab079 Antony Chazapis
    
157 22dab079 Antony Chazapis
    if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
158 22dab079 Antony Chazapis
    if if_none_match is not None:
159 22dab079 Antony Chazapis
        if if_none_match == '*' or meta['hash'] in [x.lower() for x in parse_etags(if_none_match)]:
160 22dab079 Antony Chazapis
            raise NotModified('Object Etag matches')
161 22dab079 Antony Chazapis
162 b956618e Antony Chazapis
def copy_or_move_object(request, src_path, dest_path, move=False):
163 b956618e Antony Chazapis
    """Copy or move an object"""
164 b956618e Antony Chazapis
    if type(src_path) == str:
165 b956618e Antony Chazapis
        parts = src_path.split('/')
166 b956618e Antony Chazapis
        if len(parts) < 3 or parts[0] != '':
167 b956618e Antony Chazapis
            raise BadRequest('Invalid X-Copy-From or X-Move-From header')
168 b956618e Antony Chazapis
        src_container = parts[1]
169 b956618e Antony Chazapis
        src_name = '/'.join(parts[2:])
170 b956618e Antony Chazapis
    elif type(src_path) == tuple and len(src_path) == 2:
171 b956618e Antony Chazapis
        src_container, src_name = src_path
172 b956618e Antony Chazapis
    if type(dest_path) == str:
173 b956618e Antony Chazapis
        parts = dest_path.split('/')
174 b956618e Antony Chazapis
        if len(parts) < 3 or parts[0] != '':
175 b956618e Antony Chazapis
            raise BadRequest('Invalid Destination header')
176 b956618e Antony Chazapis
        dest_container = parts[1]
177 b956618e Antony Chazapis
        dest_name = '/'.join(parts[2:])
178 b956618e Antony Chazapis
    elif type(dest_path) == tuple and len(dest_path) == 2:
179 b956618e Antony Chazapis
        dest_container, dest_name = dest_path
180 22dab079 Antony Chazapis
    
181 b956618e Antony Chazapis
    meta = get_object_meta(request)
182 22dab079 Antony Chazapis
    # Keep previous values of 'Content-Type' (if a new one is absent) and 'hash'.
183 22dab079 Antony Chazapis
    try:
184 22dab079 Antony Chazapis
        src_meta = backend.get_object_meta(request.user, src_container, src_name)
185 22dab079 Antony Chazapis
    except NameError:
186 22dab079 Antony Chazapis
        raise ItemNotFound('Container or object does not exist')
187 22dab079 Antony Chazapis
    if 'Content-Type' in meta and 'Content-Type' in src_meta:
188 22dab079 Antony Chazapis
        del(src_meta['Content-Type'])
189 22dab079 Antony Chazapis
    for k in ('Content-Type', 'hash'):
190 22dab079 Antony Chazapis
        if k in src_meta:
191 22dab079 Antony Chazapis
            meta[k] = src_meta[k]
192 22dab079 Antony Chazapis
    
193 b956618e Antony Chazapis
    try:
194 b956618e Antony Chazapis
        if move:
195 22dab079 Antony Chazapis
            backend.move_object(request.user, src_container, src_name, dest_container, dest_name, meta, replace_meta=True)
196 b956618e Antony Chazapis
        else:
197 22dab079 Antony Chazapis
            backend.copy_object(request.user, src_container, src_name, dest_container, dest_name, meta, replace_meta=True)
198 b956618e Antony Chazapis
    except NameError:
199 b956618e Antony Chazapis
        raise ItemNotFound('Container or object does not exist')
200 b956618e Antony Chazapis
201 22dab079 Antony Chazapis
def get_content_length(request):
202 22dab079 Antony Chazapis
    content_length = request.META.get('CONTENT_LENGTH')
203 22dab079 Antony Chazapis
    if not content_length:
204 22dab079 Antony Chazapis
        raise LengthRequired('Missing Content-Length header')
205 22dab079 Antony Chazapis
    try:
206 22dab079 Antony Chazapis
        content_length = int(content_length)
207 22dab079 Antony Chazapis
        if content_length < 0:
208 22dab079 Antony Chazapis
            raise ValueError
209 22dab079 Antony Chazapis
    except ValueError:
210 22dab079 Antony Chazapis
        raise BadRequest('Invalid Content-Length header')
211 22dab079 Antony Chazapis
    return content_length
212 22dab079 Antony Chazapis
213 22dab079 Antony Chazapis
def get_range(request, size):
214 b956618e Antony Chazapis
    """Parse a Range header from the request
215 b956618e Antony Chazapis
    
216 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
217 22dab079 Antony Chazapis
    or a list of (offset, length) tuples - should be further checked.
218 b956618e Antony Chazapis
    """
219 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_RANGE', '').replace(' ', '')
220 22dab079 Antony Chazapis
    if not ranges.startswith('bytes='):
221 b956618e Antony Chazapis
        return None
222 b956618e Antony Chazapis
    
223 22dab079 Antony Chazapis
    ret = []
224 22dab079 Antony Chazapis
    for r in (x.strip() for x in ranges[6:].split(',')):
225 22dab079 Antony Chazapis
        p = re.compile('^(?P<offset>\d*)-(?P<upto>\d*)$')
226 22dab079 Antony Chazapis
        m = p.match(r)
227 22dab079 Antony Chazapis
        if not m:
228 22dab079 Antony Chazapis
            return None
229 22dab079 Antony Chazapis
        offset = m.group('offset')
230 22dab079 Antony Chazapis
        upto = m.group('upto')
231 22dab079 Antony Chazapis
        if offset == '' and upto == '':
232 b956618e Antony Chazapis
            return None
233 b956618e Antony Chazapis
        
234 22dab079 Antony Chazapis
        if offset != '':
235 22dab079 Antony Chazapis
            offset = int(offset)
236 22dab079 Antony Chazapis
            if upto != '':
237 b956618e Antony Chazapis
                upto = int(upto)
238 22dab079 Antony Chazapis
                if offset > upto:
239 22dab079 Antony Chazapis
                    return None
240 22dab079 Antony Chazapis
                ret.append((offset, upto - offset + 1))
241 22dab079 Antony Chazapis
            else:
242 22dab079 Antony Chazapis
                ret.append((offset, size - offset))
243 b956618e Antony Chazapis
        else:
244 22dab079 Antony Chazapis
            length = int(upto)
245 22dab079 Antony Chazapis
            ret.append((size - length, length))
246 22dab079 Antony Chazapis
    
247 22dab079 Antony Chazapis
    return ret
248 22dab079 Antony Chazapis
249 22dab079 Antony Chazapis
def get_content_range(request):
250 22dab079 Antony Chazapis
    """Parse a Content-Range header from the request
251 22dab079 Antony Chazapis
    
252 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
253 22dab079 Antony Chazapis
    or an (offset, length, total) tuple - check as length, total may be None.
254 22dab079 Antony Chazapis
    Returns (None, None, None) if the provided range is '*/*'.
255 22dab079 Antony Chazapis
    """
256 22dab079 Antony Chazapis
    
257 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_CONTENT_RANGE', '')
258 22dab079 Antony Chazapis
    if not ranges:
259 22dab079 Antony Chazapis
        return None
260 22dab079 Antony Chazapis
    
261 22dab079 Antony Chazapis
    p = re.compile('^bytes (?P<offset>\d+)-(?P<upto>\d*)/(?P<total>(\d+|\*))$')
262 22dab079 Antony Chazapis
    m = p.match(ranges)
263 22dab079 Antony Chazapis
    if not m:
264 22dab079 Antony Chazapis
        if ranges == 'bytes */*':
265 22dab079 Antony Chazapis
            return (None, None, None)
266 22dab079 Antony Chazapis
        return None
267 22dab079 Antony Chazapis
    offset = int(m.group('offset'))
268 22dab079 Antony Chazapis
    upto = m.group('upto')
269 22dab079 Antony Chazapis
    total = m.group('total')
270 22dab079 Antony Chazapis
    if upto != '':
271 22dab079 Antony Chazapis
        upto = int(upto)
272 b956618e Antony Chazapis
    else:
273 22dab079 Antony Chazapis
        upto = None
274 22dab079 Antony Chazapis
    if total != '*':
275 22dab079 Antony Chazapis
        total = int(total)
276 22dab079 Antony Chazapis
    else:
277 22dab079 Antony Chazapis
        total = None
278 22dab079 Antony Chazapis
    if (upto and offset > upto) or \
279 22dab079 Antony Chazapis
        (total and offset >= total) or \
280 22dab079 Antony Chazapis
        (total and upto and upto >= total):
281 22dab079 Antony Chazapis
        return None
282 22dab079 Antony Chazapis
    
283 22dab079 Antony Chazapis
    if not upto:
284 22dab079 Antony Chazapis
        length = None
285 22dab079 Antony Chazapis
    else:
286 22dab079 Antony Chazapis
        length = upto - offset + 1
287 22dab079 Antony Chazapis
    return (offset, length, total)
288 b956618e Antony Chazapis
289 b956618e Antony Chazapis
def raw_input_socket(request):
290 b956618e Antony Chazapis
    """Return the socket for reading the rest of the request"""
291 b956618e Antony Chazapis
    server_software = request.META.get('SERVER_SOFTWARE')
292 b956618e Antony Chazapis
    if not server_software:
293 b956618e Antony Chazapis
        if 'wsgi.input' in request.environ:
294 b956618e Antony Chazapis
            return request.environ['wsgi.input']
295 b956618e Antony Chazapis
        raise ServiceUnavailable('Unknown server software')
296 b956618e Antony Chazapis
    if server_software.startswith('WSGIServer'):
297 b956618e Antony Chazapis
        return request.environ['wsgi.input']
298 b956618e Antony Chazapis
    elif server_software.startswith('mod_python'):
299 b956618e Antony Chazapis
        return request._req
300 b956618e Antony Chazapis
    raise ServiceUnavailable('Unknown server software')
301 b956618e Antony Chazapis
302 b956618e Antony Chazapis
MAX_UPLOAD_SIZE = 10 * (1024 * 1024) # 10MB
303 b956618e Antony Chazapis
304 22dab079 Antony Chazapis
def socket_read_iterator(sock, length=0, blocksize=4096):
305 b956618e Antony Chazapis
    """Return a maximum of blocksize data read from the socket in each iteration
306 b956618e Antony Chazapis
    
307 22dab079 Antony Chazapis
    Read up to 'length'. If 'length' is negative, will attempt a chunked read.
308 b956618e Antony Chazapis
    The maximum ammount of data read is controlled by MAX_UPLOAD_SIZE.
309 b956618e Antony Chazapis
    """
310 b956618e Antony Chazapis
    if length < 0: # Chunked transfers
311 22dab079 Antony Chazapis
        data = ''
312 b956618e Antony Chazapis
        while length < MAX_UPLOAD_SIZE:
313 22dab079 Antony Chazapis
            # Get chunk size.
314 22dab079 Antony Chazapis
            if hasattr(sock, 'readline'):
315 22dab079 Antony Chazapis
                chunk_length = sock.readline()
316 22dab079 Antony Chazapis
            else:
317 22dab079 Antony Chazapis
                chunk_length = ''
318 22dab079 Antony Chazapis
                while chunk_length[-1:] != '\n':
319 22dab079 Antony Chazapis
                    chunk_length += sock.read(1)
320 22dab079 Antony Chazapis
                chunk_length.strip()
321 b956618e Antony Chazapis
            pos = chunk_length.find(';')
322 b956618e Antony Chazapis
            if pos >= 0:
323 b956618e Antony Chazapis
                chunk_length = chunk_length[:pos]
324 b956618e Antony Chazapis
            try:
325 b956618e Antony Chazapis
                chunk_length = int(chunk_length, 16)
326 b956618e Antony Chazapis
            except Exception, e:
327 b956618e Antony Chazapis
                raise BadRequest('Bad chunk size') # TODO: Change to something more appropriate.
328 22dab079 Antony Chazapis
            # Check if done.
329 b956618e Antony Chazapis
            if chunk_length == 0:
330 22dab079 Antony Chazapis
                if len(data) > 0:
331 22dab079 Antony Chazapis
                    yield data
332 b956618e Antony Chazapis
                return
333 22dab079 Antony Chazapis
            # Get the actual data.
334 b956618e Antony Chazapis
            while chunk_length > 0:
335 22dab079 Antony Chazapis
                chunk = sock.read(min(chunk_length, blocksize))
336 22dab079 Antony Chazapis
                chunk_length -= len(chunk)
337 22dab079 Antony Chazapis
                length += len(chunk)
338 22dab079 Antony Chazapis
                data += chunk
339 22dab079 Antony Chazapis
                if len(data) >= blocksize:
340 22dab079 Antony Chazapis
                    ret = data[:blocksize]
341 22dab079 Antony Chazapis
                    data = data[blocksize:]
342 22dab079 Antony Chazapis
                    yield ret
343 22dab079 Antony Chazapis
            sock.read(2) # CRLF
344 b956618e Antony Chazapis
        # TODO: Raise something to note that maximum size is reached.
345 b956618e Antony Chazapis
    else:
346 b956618e Antony Chazapis
        if length > MAX_UPLOAD_SIZE:
347 b956618e Antony Chazapis
            # TODO: Raise something to note that maximum size is reached.
348 b956618e Antony Chazapis
            pass
349 b956618e Antony Chazapis
        while length > 0:
350 b956618e Antony Chazapis
            data = sock.read(min(length, blocksize))
351 b956618e Antony Chazapis
            length -= len(data)
352 b956618e Antony Chazapis
            yield data
353 b956618e Antony Chazapis
354 22dab079 Antony Chazapis
class ObjectWrapper(object):
355 22dab079 Antony Chazapis
    """Return the object's data block-per-block in each iteration
356 22dab079 Antony Chazapis
    
357 22dab079 Antony Chazapis
    Read from the object using the offset and length provided in each entry of the range list.
358 22dab079 Antony Chazapis
    """
359 22dab079 Antony Chazapis
    
360 22dab079 Antony Chazapis
    def __init__(self, v_account, v_container, v_object, ranges, size, hashmap, boundary):
361 22dab079 Antony Chazapis
        self.v_account = v_account
362 22dab079 Antony Chazapis
        self.v_container = v_container
363 22dab079 Antony Chazapis
        self.v_object = v_object
364 22dab079 Antony Chazapis
        self.ranges = ranges
365 22dab079 Antony Chazapis
        self.size = size
366 22dab079 Antony Chazapis
        self.hashmap = hashmap
367 22dab079 Antony Chazapis
        self.boundary = boundary
368 22dab079 Antony Chazapis
        
369 22dab079 Antony Chazapis
        self.block_index = -1
370 22dab079 Antony Chazapis
        self.block = ''
371 22dab079 Antony Chazapis
        
372 22dab079 Antony Chazapis
        self.range_index = -1
373 22dab079 Antony Chazapis
        self.offset, self.length = self.ranges[0]
374 22dab079 Antony Chazapis
    
375 22dab079 Antony Chazapis
    def __iter__(self):
376 22dab079 Antony Chazapis
        return self
377 22dab079 Antony Chazapis
    
378 22dab079 Antony Chazapis
    def part_iterator(self):
379 22dab079 Antony Chazapis
        if self.length > 0:
380 22dab079 Antony Chazapis
            # Get the block for the current offset.
381 22dab079 Antony Chazapis
            bi = int(self.offset / backend.block_size)
382 22dab079 Antony Chazapis
            if self.block_index != bi:
383 22dab079 Antony Chazapis
                try:
384 22dab079 Antony Chazapis
                    self.block = backend.get_block(self.hashmap[bi])
385 22dab079 Antony Chazapis
                except NameError:
386 22dab079 Antony Chazapis
                    raise ItemNotFound('Block does not exist')
387 22dab079 Antony Chazapis
                self.block_index = bi
388 22dab079 Antony Chazapis
            # Get the data from the block.
389 22dab079 Antony Chazapis
            bo = self.offset % backend.block_size
390 22dab079 Antony Chazapis
            bl = min(self.length, backend.block_size - bo)
391 22dab079 Antony Chazapis
            data = self.block[bo:bo + bl]
392 22dab079 Antony Chazapis
            self.offset += bl
393 22dab079 Antony Chazapis
            self.length -= bl
394 22dab079 Antony Chazapis
            return data
395 22dab079 Antony Chazapis
        else:
396 22dab079 Antony Chazapis
            raise StopIteration
397 22dab079 Antony Chazapis
    
398 22dab079 Antony Chazapis
    def next(self):
399 22dab079 Antony Chazapis
        if len(self.ranges) == 1:
400 22dab079 Antony Chazapis
            return self.part_iterator()
401 22dab079 Antony Chazapis
        if self.range_index == len(self.ranges):
402 22dab079 Antony Chazapis
            raise StopIteration
403 22dab079 Antony Chazapis
        try:
404 22dab079 Antony Chazapis
            if self.range_index == -1:
405 22dab079 Antony Chazapis
                raise StopIteration
406 22dab079 Antony Chazapis
            return self.part_iterator()
407 22dab079 Antony Chazapis
        except StopIteration:
408 22dab079 Antony Chazapis
            self.range_index += 1
409 22dab079 Antony Chazapis
            out = []
410 22dab079 Antony Chazapis
            if self.range_index < len(self.ranges):
411 22dab079 Antony Chazapis
                # Part header.
412 22dab079 Antony Chazapis
                self.offset, self.length = self.ranges[self.range_index]
413 22dab079 Antony Chazapis
                if self.range_index > 0:
414 22dab079 Antony Chazapis
                    out.append('')
415 22dab079 Antony Chazapis
                out.append('--' + self.boundary)
416 22dab079 Antony Chazapis
                out.append('Content-Range: bytes %d-%d/%d' % (self.offset, self.offset + self.length - 1, self.size))
417 22dab079 Antony Chazapis
                out.append('Content-Transfer-Encoding: binary')
418 22dab079 Antony Chazapis
                out.append('')
419 22dab079 Antony Chazapis
                out.append('')
420 22dab079 Antony Chazapis
                return '\r\n'.join(out)
421 22dab079 Antony Chazapis
            else:
422 22dab079 Antony Chazapis
                # Footer.
423 22dab079 Antony Chazapis
                out.append('')
424 22dab079 Antony Chazapis
                out.append('--' + self.boundary + '--')
425 22dab079 Antony Chazapis
                out.append('')
426 22dab079 Antony Chazapis
                return '\r\n'.join(out)
427 22dab079 Antony Chazapis
428 b956618e Antony Chazapis
def update_response_headers(request, response):
429 b956618e Antony Chazapis
    if request.serialization == 'xml':
430 b956618e Antony Chazapis
        response['Content-Type'] = 'application/xml; charset=UTF-8'
431 b956618e Antony Chazapis
    elif request.serialization == 'json':
432 b956618e Antony Chazapis
        response['Content-Type'] = 'application/json; charset=UTF-8'
433 22dab079 Antony Chazapis
    elif not response['Content-Type']:
434 b956618e Antony Chazapis
        response['Content-Type'] = 'text/plain; charset=UTF-8'
435 b956618e Antony Chazapis
436 b956618e Antony Chazapis
    if settings.TEST:
437 b956618e Antony Chazapis
        response['Date'] = format_date_time(time())
438 b956618e Antony Chazapis
439 b956618e Antony Chazapis
def render_fault(request, fault):
440 b956618e Antony Chazapis
    if settings.DEBUG or settings.TEST:
441 b956618e Antony Chazapis
        fault.details = format_exc(fault)
442 b956618e Antony Chazapis
443 b956618e Antony Chazapis
    request.serialization = 'text'
444 b956618e Antony Chazapis
    data = '\n'.join((fault.message, fault.details)) + '\n'
445 b956618e Antony Chazapis
    response = HttpResponse(data, status=fault.code)
446 b956618e Antony Chazapis
    update_response_headers(request, response)
447 b956618e Antony Chazapis
    return response
448 b956618e Antony Chazapis
449 b956618e Antony Chazapis
def request_serialization(request, format_allowed=False):
450 b956618e Antony Chazapis
    """Return the serialization format requested
451 b956618e Antony Chazapis
    
452 b956618e Antony Chazapis
    Valid formats are 'text' and 'json', 'xml' if 'format_allowed' is True.
453 b956618e Antony Chazapis
    """
454 b956618e Antony Chazapis
    if not format_allowed:
455 b956618e Antony Chazapis
        return 'text'
456 b956618e Antony Chazapis
    
457 b956618e Antony Chazapis
    format = request.GET.get('format')
458 b956618e Antony Chazapis
    if format == 'json':
459 b956618e Antony Chazapis
        return 'json'
460 b956618e Antony Chazapis
    elif format == 'xml':
461 b956618e Antony Chazapis
        return 'xml'
462 b956618e Antony Chazapis
    
463 b956618e Antony Chazapis
    for item in request.META.get('HTTP_ACCEPT', '').split(','):
464 b956618e Antony Chazapis
        accept, sep, rest = item.strip().partition(';')
465 22dab079 Antony Chazapis
        if accept == 'application/json':
466 b956618e Antony Chazapis
            return 'json'
467 b956618e Antony Chazapis
        elif accept == 'application/xml' or accept == 'text/xml':
468 b956618e Antony Chazapis
            return 'xml'
469 b956618e Antony Chazapis
    
470 b956618e Antony Chazapis
    return 'text'
471 b956618e Antony Chazapis
472 b956618e Antony Chazapis
def api_method(http_method=None, format_allowed=False):
473 b956618e Antony Chazapis
    """Decorator function for views that implement an API method"""
474 b956618e Antony Chazapis
    def decorator(func):
475 b956618e Antony Chazapis
        @wraps(func)
476 b956618e Antony Chazapis
        def wrapper(request, *args, **kwargs):
477 b956618e Antony Chazapis
            try:
478 b956618e Antony Chazapis
                if http_method and request.method != http_method:
479 b956618e Antony Chazapis
                    raise BadRequest('Method not allowed.')
480 b956618e Antony Chazapis
481 b956618e Antony Chazapis
                # The args variable may contain up to (account, container, object).
482 b956618e Antony Chazapis
                if len(args) > 1 and len(args[1]) > 256:
483 b956618e Antony Chazapis
                    raise BadRequest('Container name too large.')
484 b956618e Antony Chazapis
                if len(args) > 2 and len(args[2]) > 1024:
485 b956618e Antony Chazapis
                    raise BadRequest('Object name too large.')
486 b956618e Antony Chazapis
                
487 b956618e Antony Chazapis
                # Fill in custom request variables.
488 b956618e Antony Chazapis
                request.serialization = request_serialization(request, format_allowed)
489 b956618e Antony Chazapis
                # TODO: Authenticate.
490 b956618e Antony Chazapis
                request.user = "test"
491 b956618e Antony Chazapis
                
492 b956618e Antony Chazapis
                response = func(request, *args, **kwargs)
493 b956618e Antony Chazapis
                update_response_headers(request, response)
494 b956618e Antony Chazapis
                return response
495 b956618e Antony Chazapis
            except Fault, fault:
496 b956618e Antony Chazapis
                return render_fault(request, fault)
497 b956618e Antony Chazapis
            except BaseException, e:
498 b956618e Antony Chazapis
                logger.exception('Unexpected error: %s' % e)
499 b956618e Antony Chazapis
                fault = ServiceUnavailable('Unexpected error')
500 b956618e Antony Chazapis
                return render_fault(request, fault)
501 b956618e Antony Chazapis
        return wrapper
502 b956618e Antony Chazapis
    return decorator