Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ a9420179

History | View | Annotate | Download (39.7 kB)

1 2e662088 Antony Chazapis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 2715ade4 Sofia Papagiannaki
#
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 2715ade4 Sofia Papagiannaki
#
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 2715ade4 Sofia Papagiannaki
#
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 2715ade4 Sofia Papagiannaki
#
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 2715ade4 Sofia Papagiannaki
#
29 5635f9ef Antony Chazapis
# The views and conclusions contained in the software and
30 5635f9ef Antony Chazapis
# documentation are those of the authors and should not be
31 5635f9ef Antony Chazapis
# interpreted as representing official policies, either expressed
32 5635f9ef Antony Chazapis
# or implied, of GRNET S.A.
33 5635f9ef Antony Chazapis
34 b956618e Antony Chazapis
from functools import wraps
35 b956618e Antony Chazapis
from time import time
36 b956618e Antony Chazapis
from traceback import format_exc
37 b956618e Antony Chazapis
from wsgiref.handlers import format_date_time
38 0d4ea090 Antony Chazapis
from binascii import hexlify, unhexlify
39 e11087c2 Antony Chazapis
from datetime import datetime, tzinfo, timedelta
40 9fefc052 Antony Chazapis
from urllib import quote, unquote
41 b956618e Antony Chazapis
42 b956618e Antony Chazapis
from django.conf import settings
43 b956618e Antony Chazapis
from django.http import HttpResponse
44 6b6b6c1e Antony Chazapis
from django.template.loader import render_to_string
45 1993fea9 Antony Chazapis
from django.utils import simplejson as json
46 22dab079 Antony Chazapis
from django.utils.http import http_date, parse_etags
47 fb064032 Antony Chazapis
from django.utils.encoding import smart_unicode, smart_str
48 817890f2 Antony Chazapis
from django.core.files.uploadhandler import FileUploadHandler
49 817890f2 Antony Chazapis
from django.core.files.uploadedfile import UploadedFile
50 b956618e Antony Chazapis
51 6e147ecc Antony Chazapis
from synnefo.lib.parsedate import parse_http_date_safe, parse_http_date
52 dfdf4802 Sofia Papagiannaki
from synnefo.lib.astakos import get_user
53 5a96180b Antony Chazapis
54 2715ade4 Sofia Papagiannaki
from pithos.api.faults import (
55 2715ade4 Sofia Papagiannaki
    Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound,
56 2715ade4 Sofia Papagiannaki
    Conflict, LengthRequired, PreconditionFailed, RequestEntityTooLarge,
57 2715ade4 Sofia Papagiannaki
    RangeNotSatisfiable, InternalServerError, NotImplemented)
58 bb4eafc6 Antony Chazapis
from pithos.api.short_url import encode_url
59 a7dff008 Antony Chazapis
from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION,
60 761c2b3c root
                                 BACKEND_BLOCK_MODULE, BACKEND_BLOCK_PATH,
61 761c2b3c root
                                 BACKEND_BLOCK_UMASK,
62 f4fbb0fa Sofia Papagiannaki
                                 BACKEND_QUEUE_MODULE, BACKEND_QUEUE_HOSTS,
63 f4fbb0fa Sofia Papagiannaki
                                 BACKEND_QUEUE_EXCHANGE,
64 3173699c Sofia Papagiannaki
                                 QUOTAHOLDER_URL, QUOTAHOLDER_TOKEN,
65 3173699c Sofia Papagiannaki
                                 BACKEND_QUOTA, BACKEND_VERSIONING,
66 b1dadd0e Sofia Papagiannaki
                                 BACKEND_FREE_VERSIONING,
67 761c2b3c root
                                 AUTHENTICATION_URL, AUTHENTICATION_USERS,
68 7dc78140 Sofia Papagiannaki
                                 SERVICE_TOKEN, COOKIE_NAME, USER_INFO_URL,
69 47462eda Filippos Giannakos
                                 RADOS_STORAGE, RADOS_POOL_BLOCKS,
70 47462eda Filippos Giannakos
                                 RADOS_POOL_MAPS)
71 39593b2b Giorgos Verigakis
from pithos.backends import connect_backend
72 32454501 Sofia Papagiannaki
from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists,
73 32454501 Sofia Papagiannaki
                                  VersionNotExists)
74 32454501 Sofia Papagiannaki
from synnefo.lib.astakos import get_user_uuid, get_username
75 b956618e Antony Chazapis
76 b956618e Antony Chazapis
import logging
77 22dab079 Antony Chazapis
import re
78 cbfb6636 Sofia Papagiannaki
import hashlib
79 7bef5750 Antony Chazapis
import uuid
80 c48acbfd Sofia Papagiannaki
import decimal
81 8c793655 Antony Chazapis
82 b956618e Antony Chazapis
83 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
84 b956618e Antony Chazapis
85 b956618e Antony Chazapis
86 e11087c2 Antony Chazapis
class UTC(tzinfo):
87 2715ade4 Sofia Papagiannaki
    def utcoffset(self, dt):
88 2715ade4 Sofia Papagiannaki
        return timedelta(0)
89 e11087c2 Antony Chazapis
90 2715ade4 Sofia Papagiannaki
    def tzname(self, dt):
91 2715ade4 Sofia Papagiannaki
        return 'UTC'
92 e11087c2 Antony Chazapis
93 2715ade4 Sofia Papagiannaki
    def dst(self, dt):
94 2715ade4 Sofia Papagiannaki
        return timedelta(0)
95 e11087c2 Antony Chazapis
96 e11087c2 Antony Chazapis
97 c48acbfd Sofia Papagiannaki
def json_encode_decimal(obj):
98 c48acbfd Sofia Papagiannaki
    if isinstance(obj, decimal.Decimal):
99 c48acbfd Sofia Papagiannaki
        return str(obj)
100 c48acbfd Sofia Papagiannaki
    raise TypeError(repr(obj) + " is not JSON serializable")
101 c48acbfd Sofia Papagiannaki
102 2715ade4 Sofia Papagiannaki
103 e11087c2 Antony Chazapis
def isoformat(d):
104 2715ade4 Sofia Papagiannaki
    """Return an ISO8601 date string that includes a timezone."""
105 2715ade4 Sofia Papagiannaki
106 2715ade4 Sofia Papagiannaki
    return d.replace(tzinfo=UTC()).isoformat()
107 e11087c2 Antony Chazapis
108 e11087c2 Antony Chazapis
109 804e8fe7 Antony Chazapis
def rename_meta_key(d, old, new):
110 804e8fe7 Antony Chazapis
    if old not in d:
111 804e8fe7 Antony Chazapis
        return
112 804e8fe7 Antony Chazapis
    d[new] = d[old]
113 804e8fe7 Antony Chazapis
    del(d[old])
114 804e8fe7 Antony Chazapis
115 2715ade4 Sofia Papagiannaki
116 02c0c3fa Antony Chazapis
def printable_header_dict(d):
117 b956618e Antony Chazapis
    """Format a meta dictionary for printing out json/xml.
118 2715ade4 Sofia Papagiannaki

119 804e8fe7 Antony Chazapis
    Convert all keys to lower case and replace dashes with underscores.
120 804e8fe7 Antony Chazapis
    Format 'last_modified' timestamp.
121 b956618e Antony Chazapis
    """
122 2715ade4 Sofia Papagiannaki
123 371d907a Antony Chazapis
    if 'last_modified' in d and d['last_modified']:
124 2715ade4 Sofia Papagiannaki
        d['last_modified'] = isoformat(
125 2715ade4 Sofia Papagiannaki
            datetime.fromtimestamp(d['last_modified']))
126 b956618e Antony Chazapis
    return dict([(k.lower().replace('-', '_'), v) for k, v in d.iteritems()])
127 b956618e Antony Chazapis
128 2715ade4 Sofia Papagiannaki
129 02c0c3fa Antony Chazapis
def format_header_key(k):
130 58a6c894 Antony Chazapis
    """Convert underscores to dashes and capitalize intra-dash strings."""
131 b956618e Antony Chazapis
    return '-'.join([x.capitalize() for x in k.replace('_', '-').split('-')])
132 b956618e Antony Chazapis
133 2715ade4 Sofia Papagiannaki
134 02c0c3fa Antony Chazapis
def get_header_prefix(request, prefix):
135 02c0c3fa Antony Chazapis
    """Get all prefix-* request headers in a dict. Reformat keys with format_header_key()."""
136 2715ade4 Sofia Papagiannaki
137 b956618e Antony Chazapis
    prefix = 'HTTP_' + prefix.upper().replace('-', '_')
138 3ab38c43 Antony Chazapis
    # TODO: Document or remove '~' replacing.
139 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)])
140 02c0c3fa Antony Chazapis
141 2715ade4 Sofia Papagiannaki
142 62bf8157 Antony Chazapis
def check_meta_headers(meta):
143 62bf8157 Antony Chazapis
    if len(meta) > 90:
144 62bf8157 Antony Chazapis
        raise BadRequest('Too many headers.')
145 62bf8157 Antony Chazapis
    for k, v in meta.iteritems():
146 62bf8157 Antony Chazapis
        if len(k) > 128:
147 62bf8157 Antony Chazapis
            raise BadRequest('Header name too large.')
148 62bf8157 Antony Chazapis
        if len(v) > 256:
149 62bf8157 Antony Chazapis
            raise BadRequest('Header value too large.')
150 62bf8157 Antony Chazapis
151 2715ade4 Sofia Papagiannaki
152 02c0c3fa Antony Chazapis
def get_account_headers(request):
153 02c0c3fa Antony Chazapis
    meta = get_header_prefix(request, 'X-Account-Meta-')
154 62bf8157 Antony Chazapis
    check_meta_headers(meta)
155 02c0c3fa Antony Chazapis
    groups = {}
156 02c0c3fa Antony Chazapis
    for k, v in get_header_prefix(request, 'X-Account-Group-').iteritems():
157 02c0c3fa Antony Chazapis
        n = k[16:].lower()
158 02c0c3fa Antony Chazapis
        if '-' in n or '_' in n:
159 02c0c3fa Antony Chazapis
            raise BadRequest('Bad characters in group name')
160 02c0c3fa Antony Chazapis
        groups[n] = v.replace(' ', '').split(',')
161 40d6b76d Antony Chazapis
        while '' in groups[n]:
162 02c0c3fa Antony Chazapis
            groups[n].remove('')
163 02c0c3fa Antony Chazapis
    return meta, groups
164 02c0c3fa Antony Chazapis
165 2715ade4 Sofia Papagiannaki
166 4d366a03 Sofia Papagiannaki
def put_account_translation_headers(response, accounts):
167 4d366a03 Sofia Papagiannaki
    for x in accounts:
168 4d366a03 Sofia Papagiannaki
        k = smart_str('X-Account-Presentation-%s' % x, strings_only=True)
169 4d366a03 Sofia Papagiannaki
        v = smart_str(retrieve_username(x), strings_only=True)
170 4d366a03 Sofia Papagiannaki
        response[k] = v
171 4d366a03 Sofia Papagiannaki
172 4d366a03 Sofia Papagiannaki
173 647a5f48 Antony Chazapis
def put_account_headers(response, meta, groups, policy):
174 f6c97079 Antony Chazapis
    if 'count' in meta:
175 f6c97079 Antony Chazapis
        response['X-Account-Container-Count'] = meta['count']
176 f6c97079 Antony Chazapis
    if 'bytes' in meta:
177 f6c97079 Antony Chazapis
        response['X-Account-Bytes-Used'] = meta['bytes']
178 d065f612 Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
179 b956618e Antony Chazapis
    for k in [x for x in meta.keys() if x.startswith('X-Account-Meta-')]:
180 2715ade4 Sofia Papagiannaki
        response[smart_str(
181 2715ade4 Sofia Papagiannaki
            k, strings_only=True)] = smart_str(meta[k], strings_only=True)
182 83dd59c5 Antony Chazapis
    if 'until_timestamp' in meta:
183 2715ade4 Sofia Papagiannaki
        response['X-Account-Until-Timestamp'] = http_date(
184 2715ade4 Sofia Papagiannaki
            int(meta['until_timestamp']))
185 02c0c3fa Antony Chazapis
    for k, v in groups.iteritems():
186 e7b51248 Sofia Papagiannaki
        k = smart_str(k, strings_only=True)
187 e7b51248 Sofia Papagiannaki
        k = format_header_key('X-Account-Group-' + k)
188 e7b51248 Sofia Papagiannaki
        v = smart_str(','.join(v), strings_only=True)
189 e7b51248 Sofia Papagiannaki
        response[k] = v
190 647a5f48 Antony Chazapis
    for k, v in policy.iteritems():
191 647a5f48 Antony Chazapis
        response[smart_str(format_header_key('X-Account-Policy-' + k), strings_only=True)] = smart_str(v, strings_only=True)
192 647a5f48 Antony Chazapis
193 2715ade4 Sofia Papagiannaki
194 02c0c3fa Antony Chazapis
def get_container_headers(request):
195 02c0c3fa Antony Chazapis
    meta = get_header_prefix(request, 'X-Container-Meta-')
196 62bf8157 Antony Chazapis
    check_meta_headers(meta)
197 3ab38c43 Antony Chazapis
    policy = dict([(k[19:].lower(), v.replace(' ', '')) for k, v in get_header_prefix(request, 'X-Container-Policy-').iteritems()])
198 3ab38c43 Antony Chazapis
    return meta, policy
199 b956618e Antony Chazapis
200 2715ade4 Sofia Papagiannaki
201 39593b2b Giorgos Verigakis
def put_container_headers(request, response, meta, policy):
202 f6c97079 Antony Chazapis
    if 'count' in meta:
203 f6c97079 Antony Chazapis
        response['X-Container-Object-Count'] = meta['count']
204 f6c97079 Antony Chazapis
    if 'bytes' in meta:
205 f6c97079 Antony Chazapis
        response['X-Container-Bytes-Used'] = meta['bytes']
206 58a6c894 Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
207 b956618e Antony Chazapis
    for k in [x for x in meta.keys() if x.startswith('X-Container-Meta-')]:
208 2715ade4 Sofia Papagiannaki
        response[smart_str(
209 2715ade4 Sofia Papagiannaki
            k, strings_only=True)] = smart_str(meta[k], strings_only=True)
210 2715ade4 Sofia Papagiannaki
    l = [smart_str(x, strings_only=True) for x in meta['object_meta']
211 2715ade4 Sofia Papagiannaki
         if x.startswith('X-Object-Meta-')]
212 e7b51248 Sofia Papagiannaki
    response['X-Container-Object-Meta'] = ','.join([x[14:] for x in l])
213 39593b2b Giorgos Verigakis
    response['X-Container-Block-Size'] = request.backend.block_size
214 39593b2b Giorgos Verigakis
    response['X-Container-Block-Hash'] = request.backend.hash_algorithm
215 83dd59c5 Antony Chazapis
    if 'until_timestamp' in meta:
216 2715ade4 Sofia Papagiannaki
        response['X-Container-Until-Timestamp'] = http_date(
217 2715ade4 Sofia Papagiannaki
            int(meta['until_timestamp']))
218 3ab38c43 Antony Chazapis
    for k, v in policy.iteritems():
219 e7b51248 Sofia Papagiannaki
        response[smart_str(format_header_key('X-Container-Policy-' + k), strings_only=True)] = smart_str(v, strings_only=True)
220 b956618e Antony Chazapis
221 2715ade4 Sofia Papagiannaki
222 02c0c3fa Antony Chazapis
def get_object_headers(request):
223 66ce2ca5 Antony Chazapis
    content_type = request.META.get('CONTENT_TYPE', None)
224 02c0c3fa Antony Chazapis
    meta = get_header_prefix(request, 'X-Object-Meta-')
225 62bf8157 Antony Chazapis
    check_meta_headers(meta)
226 b956618e Antony Chazapis
    if request.META.get('HTTP_CONTENT_ENCODING'):
227 b956618e Antony Chazapis
        meta['Content-Encoding'] = request.META['HTTP_CONTENT_ENCODING']
228 22dab079 Antony Chazapis
    if request.META.get('HTTP_CONTENT_DISPOSITION'):
229 22dab079 Antony Chazapis
        meta['Content-Disposition'] = request.META['HTTP_CONTENT_DISPOSITION']
230 b956618e Antony Chazapis
    if request.META.get('HTTP_X_OBJECT_MANIFEST'):
231 b956618e Antony Chazapis
        meta['X-Object-Manifest'] = request.META['HTTP_X_OBJECT_MANIFEST']
232 66ce2ca5 Antony Chazapis
    return content_type, meta, get_sharing(request), get_public(request)
233 b956618e Antony Chazapis
234 2715ade4 Sofia Papagiannaki
235 3ab38c43 Antony Chazapis
def put_object_headers(response, meta, restricted=False):
236 33b4e4a6 Antony Chazapis
    response['ETag'] = meta['checksum']
237 b956618e Antony Chazapis
    response['Content-Length'] = meta['bytes']
238 66ce2ca5 Antony Chazapis
    response['Content-Type'] = meta.get('type', 'application/octet-stream')
239 b956618e Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
240 3ab38c43 Antony Chazapis
    if not restricted:
241 4a1c29ea Antony Chazapis
        response['X-Object-Hash'] = meta['hash']
242 37bee317 Antony Chazapis
        response['X-Object-UUID'] = meta['uuid']
243 88dd5c4d Sofia Papagiannaki
        modified_by = retrieve_username(meta['modified_by'])
244 2715ade4 Sofia Papagiannaki
        response['X-Object-Modified-By'] = smart_str(
245 88dd5c4d Sofia Papagiannaki
            modified_by, strings_only=True)
246 7bef5750 Antony Chazapis
        response['X-Object-Version'] = meta['version']
247 2715ade4 Sofia Papagiannaki
        response['X-Object-Version-Timestamp'] = http_date(
248 2715ade4 Sofia Papagiannaki
            int(meta['version_timestamp']))
249 7bef5750 Antony Chazapis
        for k in [x for x in meta.keys() if x.startswith('X-Object-Meta-')]:
250 2715ade4 Sofia Papagiannaki
            response[smart_str(
251 2715ade4 Sofia Papagiannaki
                k, strings_only=True)] = smart_str(meta[k], strings_only=True)
252 2715ade4 Sofia Papagiannaki
        for k in (
253 2715ade4 Sofia Papagiannaki
            'Content-Encoding', 'Content-Disposition', 'X-Object-Manifest',
254 2715ade4 Sofia Papagiannaki
            'X-Object-Sharing', 'X-Object-Shared-By', 'X-Object-Allowed-To',
255 2715ade4 Sofia Papagiannaki
                'X-Object-Public'):
256 7bef5750 Antony Chazapis
            if k in meta:
257 e7b51248 Sofia Papagiannaki
                response[k] = smart_str(meta[k], strings_only=True)
258 7bef5750 Antony Chazapis
    else:
259 c9af0703 Antony Chazapis
        for k in ('Content-Encoding', 'Content-Disposition'):
260 7bef5750 Antony Chazapis
            if k in meta:
261 e8d003e8 Antony Chazapis
                response[k] = smart_str(meta[k], strings_only=True)
262 b956618e Antony Chazapis
263 2715ade4 Sofia Papagiannaki
264 8cb45c13 Antony Chazapis
def update_manifest_meta(request, v_account, meta):
265 8cb45c13 Antony Chazapis
    """Update metadata if the object has an X-Object-Manifest."""
266 2715ade4 Sofia Papagiannaki
267 8cb45c13 Antony Chazapis
    if 'X-Object-Manifest' in meta:
268 4a1c29ea Antony Chazapis
        etag = ''
269 8cb45c13 Antony Chazapis
        bytes = 0
270 8cb45c13 Antony Chazapis
        try:
271 2715ade4 Sofia Papagiannaki
            src_container, src_name = split_container_object_string(
272 2715ade4 Sofia Papagiannaki
                '/' + meta['X-Object-Manifest'])
273 2715ade4 Sofia Papagiannaki
            objects = request.backend.list_objects(
274 2715ade4 Sofia Papagiannaki
                request.user_uniq, v_account,
275 2715ade4 Sofia Papagiannaki
                src_container, prefix=src_name, virtual=False)
276 8cb45c13 Antony Chazapis
            for x in objects:
277 61efb530 Antony Chazapis
                src_meta = request.backend.get_object_meta(request.user_uniq,
278 2715ade4 Sofia Papagiannaki
                                                           v_account, src_container, x[0], 'pithos', x[1])
279 33b4e4a6 Antony Chazapis
                etag += src_meta['checksum']
280 8cb45c13 Antony Chazapis
                bytes += src_meta['bytes']
281 8cb45c13 Antony Chazapis
        except:
282 8cb45c13 Antony Chazapis
            # Ignore errors.
283 8cb45c13 Antony Chazapis
            return
284 8cb45c13 Antony Chazapis
        meta['bytes'] = bytes
285 8cb45c13 Antony Chazapis
        md5 = hashlib.md5()
286 4a1c29ea Antony Chazapis
        md5.update(etag)
287 33b4e4a6 Antony Chazapis
        meta['checksum'] = md5.hexdigest().lower()
288 8cb45c13 Antony Chazapis
289 9839cc96 root
def is_uuid(str):
290 9839cc96 root
    try:
291 9839cc96 root
        uuid.UUID(str)
292 9839cc96 root
    except ValueError:
293 9839cc96 root
       return False
294 9839cc96 root
    else:
295 9839cc96 root
       return True
296 2715ade4 Sofia Papagiannaki
297 32454501 Sofia Papagiannaki
def retrieve_username(uuid):
298 32454501 Sofia Papagiannaki
    try:
299 32454501 Sofia Papagiannaki
        return get_username(
300 32454501 Sofia Papagiannaki
            SERVICE_TOKEN, uuid, USER_INFO_URL, AUTHENTICATION_USERS)
301 32454501 Sofia Papagiannaki
    except:
302 32454501 Sofia Papagiannaki
        # if it fails just leave the metadata intact
303 32454501 Sofia Papagiannaki
        return uuid
304 32454501 Sofia Papagiannaki
305 32454501 Sofia Papagiannaki
def retrieve_uuid(username):
306 9839cc96 root
    if is_uuid(username):
307 7f1f0464 Georgios D. Tsoukalas
        return username
308 88dd5c4d Sofia Papagiannaki
309 32454501 Sofia Papagiannaki
    try:
310 32454501 Sofia Papagiannaki
        return get_user_uuid(
311 32454501 Sofia Papagiannaki
            SERVICE_TOKEN, username, USER_INFO_URL, AUTHENTICATION_USERS)
312 32454501 Sofia Papagiannaki
    except Exception, e:
313 32454501 Sofia Papagiannaki
        if e.args:
314 32454501 Sofia Papagiannaki
            status = e.args[-1]
315 32454501 Sofia Papagiannaki
            if status == 404:
316 32454501 Sofia Papagiannaki
                raise ItemNotExists(username)
317 32454501 Sofia Papagiannaki
        raise
318 32454501 Sofia Papagiannaki
319 32454501 Sofia Papagiannaki
def replace_permissions_username(holder):
320 32454501 Sofia Papagiannaki
    try:
321 32454501 Sofia Papagiannaki
        # check first for a group permission
322 32454501 Sofia Papagiannaki
        account, group = holder.split(':')
323 32454501 Sofia Papagiannaki
    except ValueError:
324 32454501 Sofia Papagiannaki
        return retrieve_uuid(holder)
325 32454501 Sofia Papagiannaki
    else:
326 32454501 Sofia Papagiannaki
        return ':'.join([retrieve_uuid(account), group])
327 32454501 Sofia Papagiannaki
328 32454501 Sofia Papagiannaki
def replace_permissions_uuid(holder):
329 32454501 Sofia Papagiannaki
    try:
330 32454501 Sofia Papagiannaki
        # check first for a group permission
331 32454501 Sofia Papagiannaki
        account, group = holder.split(':')
332 32454501 Sofia Papagiannaki
    except ValueError:
333 32454501 Sofia Papagiannaki
        return retrieve_username(holder)
334 32454501 Sofia Papagiannaki
    else:
335 32454501 Sofia Papagiannaki
        return ':'.join([retrieve_username(account), group])
336 32454501 Sofia Papagiannaki
337 067cf1fc Antony Chazapis
def update_sharing_meta(request, permissions, v_account, v_container, v_object, meta):
338 cca6c617 Antony Chazapis
    if permissions is None:
339 cca6c617 Antony Chazapis
        return
340 067cf1fc Antony Chazapis
    allowed, perm_path, perms = permissions
341 cca6c617 Antony Chazapis
    if len(perms) == 0:
342 cca6c617 Antony Chazapis
        return
343 32454501 Sofia Papagiannaki
344 32454501 Sofia Papagiannaki
    perms['read'] = [replace_permissions_uuid(x) for x in perms.get('read', [])]
345 32454501 Sofia Papagiannaki
    perms['write'] = \
346 32454501 Sofia Papagiannaki
        [replace_permissions_uuid(x) for x in perms.get('write', [])]
347 32454501 Sofia Papagiannaki
348 3436eeb0 Antony Chazapis
    ret = []
349 32454501 Sofia Papagiannaki
350 cca6c617 Antony Chazapis
    r = ','.join(perms.get('read', []))
351 3436eeb0 Antony Chazapis
    if r:
352 3436eeb0 Antony Chazapis
        ret.append('read=' + r)
353 cca6c617 Antony Chazapis
    w = ','.join(perms.get('write', []))
354 3436eeb0 Antony Chazapis
    if w:
355 3436eeb0 Antony Chazapis
        ret.append('write=' + w)
356 cca6c617 Antony Chazapis
    meta['X-Object-Sharing'] = '; '.join(ret)
357 cca6c617 Antony Chazapis
    if '/'.join((v_account, v_container, v_object)) != perm_path:
358 cca6c617 Antony Chazapis
        meta['X-Object-Shared-By'] = perm_path
359 61efb530 Antony Chazapis
    if request.user_uniq != v_account:
360 067cf1fc Antony Chazapis
        meta['X-Object-Allowed-To'] = allowed
361 3436eeb0 Antony Chazapis
362 2715ade4 Sofia Papagiannaki
363 e0f916bb Antony Chazapis
def update_public_meta(public, meta):
364 e0f916bb Antony Chazapis
    if not public:
365 e0f916bb Antony Chazapis
        return
366 bb4eafc6 Antony Chazapis
    meta['X-Object-Public'] = '/public/' + encode_url(public)
367 e0f916bb Antony Chazapis
368 2715ade4 Sofia Papagiannaki
369 b956618e Antony Chazapis
def validate_modification_preconditions(request, meta):
370 58a6c894 Antony Chazapis
    """Check that the modified timestamp conforms with the preconditions set."""
371 2715ade4 Sofia Papagiannaki
372 22dab079 Antony Chazapis
    if 'modified' not in meta:
373 2715ade4 Sofia Papagiannaki
        return  # TODO: Always return?
374 2715ade4 Sofia Papagiannaki
375 b956618e Antony Chazapis
    if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
376 b956618e Antony Chazapis
    if if_modified_since is not None:
377 b956618e Antony Chazapis
        if_modified_since = parse_http_date_safe(if_modified_since)
378 22dab079 Antony Chazapis
    if if_modified_since is not None and int(meta['modified']) <= if_modified_since:
379 3e5d2f38 Antony Chazapis
        raise NotModified('Resource has not been modified')
380 2715ade4 Sofia Papagiannaki
381 b956618e Antony Chazapis
    if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')
382 b956618e Antony Chazapis
    if if_unmodified_since is not None:
383 b956618e Antony Chazapis
        if_unmodified_since = parse_http_date_safe(if_unmodified_since)
384 22dab079 Antony Chazapis
    if if_unmodified_since is not None and int(meta['modified']) > if_unmodified_since:
385 3e5d2f38 Antony Chazapis
        raise PreconditionFailed('Resource has been modified')
386 b956618e Antony Chazapis
387 2715ade4 Sofia Papagiannaki
388 22dab079 Antony Chazapis
def validate_matching_preconditions(request, meta):
389 58a6c894 Antony Chazapis
    """Check that the ETag conforms with the preconditions set."""
390 2715ade4 Sofia Papagiannaki
391 33b4e4a6 Antony Chazapis
    etag = meta['checksum']
392 33b4e4a6 Antony Chazapis
    if not etag:
393 33b4e4a6 Antony Chazapis
        etag = None
394 2715ade4 Sofia Papagiannaki
395 22dab079 Antony Chazapis
    if_match = request.META.get('HTTP_IF_MATCH')
396 a8326bef Antony Chazapis
    if if_match is not None:
397 4a1c29ea Antony Chazapis
        if etag is None:
398 a8326bef Antony Chazapis
            raise PreconditionFailed('Resource does not exist')
399 4a1c29ea Antony Chazapis
        if if_match != '*' and etag not in [x.lower() for x in parse_etags(if_match)]:
400 a8326bef Antony Chazapis
            raise PreconditionFailed('Resource ETag does not match')
401 2715ade4 Sofia Papagiannaki
402 22dab079 Antony Chazapis
    if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
403 22dab079 Antony Chazapis
    if if_none_match is not None:
404 a8326bef Antony Chazapis
        # TODO: If this passes, must ignore If-Modified-Since header.
405 4a1c29ea Antony Chazapis
        if etag is not None:
406 4a1c29ea Antony Chazapis
            if if_none_match == '*' or etag in [x.lower() for x in parse_etags(if_none_match)]:
407 a8326bef Antony Chazapis
                # TODO: Continue if an If-Modified-Since header is present.
408 a8326bef Antony Chazapis
                if request.method in ('HEAD', 'GET'):
409 a8326bef Antony Chazapis
                    raise NotModified('Resource ETag matches')
410 a8326bef Antony Chazapis
                raise PreconditionFailed('Resource exists or ETag matches')
411 22dab079 Antony Chazapis
412 2715ade4 Sofia Papagiannaki
413 83dd59c5 Antony Chazapis
def split_container_object_string(s):
414 6d817842 Antony Chazapis
    if not len(s) > 0 or s[0] != '/':
415 6d817842 Antony Chazapis
        raise ValueError
416 6d817842 Antony Chazapis
    s = s[1:]
417 8cb45c13 Antony Chazapis
    pos = s.find('/')
418 22d7b01e Antony Chazapis
    if pos == -1 or pos == len(s) - 1:
419 83dd59c5 Antony Chazapis
        raise ValueError
420 8cb45c13 Antony Chazapis
    return s[:pos], s[(pos + 1):]
421 83dd59c5 Antony Chazapis
422 2715ade4 Sofia Papagiannaki
423 4d15c94e Sofia Papagiannaki
def copy_or_move_object(request, src_account, src_container, src_name, dest_account, dest_container, dest_name, move=False, delimiter=None):
424 58a6c894 Antony Chazapis
    """Copy or move an object."""
425 2715ade4 Sofia Papagiannaki
426 53cff70c Antony Chazapis
    if 'ignore_content_type' in request.GET and 'CONTENT_TYPE' in request.META:
427 53cff70c Antony Chazapis
        del(request.META['CONTENT_TYPE'])
428 66ce2ca5 Antony Chazapis
    content_type, meta, permissions, public = get_object_headers(request)
429 79bb41b7 Antony Chazapis
    src_version = request.META.get('HTTP_X_SOURCE_VERSION')
430 b956618e Antony Chazapis
    try:
431 b956618e Antony Chazapis
        if move:
432 2715ade4 Sofia Papagiannaki
            version_id = request.backend.move_object(
433 2715ade4 Sofia Papagiannaki
                request.user_uniq, src_account, src_container, src_name,
434 2715ade4 Sofia Papagiannaki
                dest_account, dest_container, dest_name,
435 2715ade4 Sofia Papagiannaki
                content_type, 'pithos', meta, False, permissions, delimiter)
436 b956618e Antony Chazapis
        else:
437 2715ade4 Sofia Papagiannaki
            version_id = request.backend.copy_object(
438 2715ade4 Sofia Papagiannaki
                request.user_uniq, src_account, src_container, src_name,
439 2715ade4 Sofia Papagiannaki
                dest_account, dest_container, dest_name,
440 2715ade4 Sofia Papagiannaki
                content_type, 'pithos', meta, False, permissions, src_version, delimiter)
441 cca6c617 Antony Chazapis
    except NotAllowedError:
442 297513ba Antony Chazapis
        raise Forbidden('Not allowed')
443 7efc9f86 Sofia Papagiannaki
    except (ItemNotExists, VersionNotExists):
444 b956618e Antony Chazapis
        raise ItemNotFound('Container or object does not exist')
445 3436eeb0 Antony Chazapis
    except ValueError:
446 3436eeb0 Antony Chazapis
        raise BadRequest('Invalid sharing header')
447 5df6c6d1 Antony Chazapis
    except QuotaError:
448 5df6c6d1 Antony Chazapis
        raise RequestEntityTooLarge('Quota exceeded')
449 e0f916bb Antony Chazapis
    if public is not None:
450 e0f916bb Antony Chazapis
        try:
451 61efb530 Antony Chazapis
            request.backend.update_object_public(request.user_uniq, dest_account, dest_container, dest_name, public)
452 e0f916bb Antony Chazapis
        except NotAllowedError:
453 297513ba Antony Chazapis
            raise Forbidden('Not allowed')
454 7efc9f86 Sofia Papagiannaki
        except ItemNotExists:
455 e0f916bb Antony Chazapis
            raise ItemNotFound('Object does not exist')
456 7dd293a0 Antony Chazapis
    return version_id
457 b956618e Antony Chazapis
458 2715ade4 Sofia Papagiannaki
459 1495b972 Antony Chazapis
def get_int_parameter(p):
460 83dd59c5 Antony Chazapis
    if p is not None:
461 58a6c894 Antony Chazapis
        try:
462 83dd59c5 Antony Chazapis
            p = int(p)
463 58a6c894 Antony Chazapis
        except ValueError:
464 58a6c894 Antony Chazapis
            return None
465 83dd59c5 Antony Chazapis
        if p < 0:
466 58a6c894 Antony Chazapis
            return None
467 83dd59c5 Antony Chazapis
    return p
468 58a6c894 Antony Chazapis
469 2715ade4 Sofia Papagiannaki
470 22dab079 Antony Chazapis
def get_content_length(request):
471 1495b972 Antony Chazapis
    content_length = get_int_parameter(request.META.get('CONTENT_LENGTH'))
472 1495b972 Antony Chazapis
    if content_length is None:
473 1495b972 Antony Chazapis
        raise LengthRequired('Missing or invalid Content-Length header')
474 22dab079 Antony Chazapis
    return content_length
475 22dab079 Antony Chazapis
476 2715ade4 Sofia Papagiannaki
477 22dab079 Antony Chazapis
def get_range(request, size):
478 58a6c894 Antony Chazapis
    """Parse a Range header from the request.
479 2715ade4 Sofia Papagiannaki

480 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
481 22dab079 Antony Chazapis
    or a list of (offset, length) tuples - should be further checked.
482 b956618e Antony Chazapis
    """
483 2715ade4 Sofia Papagiannaki
484 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_RANGE', '').replace(' ', '')
485 22dab079 Antony Chazapis
    if not ranges.startswith('bytes='):
486 b956618e Antony Chazapis
        return None
487 2715ade4 Sofia Papagiannaki
488 22dab079 Antony Chazapis
    ret = []
489 22dab079 Antony Chazapis
    for r in (x.strip() for x in ranges[6:].split(',')):
490 22dab079 Antony Chazapis
        p = re.compile('^(?P<offset>\d*)-(?P<upto>\d*)$')
491 22dab079 Antony Chazapis
        m = p.match(r)
492 22dab079 Antony Chazapis
        if not m:
493 22dab079 Antony Chazapis
            return None
494 22dab079 Antony Chazapis
        offset = m.group('offset')
495 22dab079 Antony Chazapis
        upto = m.group('upto')
496 22dab079 Antony Chazapis
        if offset == '' and upto == '':
497 b956618e Antony Chazapis
            return None
498 2715ade4 Sofia Papagiannaki
499 22dab079 Antony Chazapis
        if offset != '':
500 22dab079 Antony Chazapis
            offset = int(offset)
501 22dab079 Antony Chazapis
            if upto != '':
502 b956618e Antony Chazapis
                upto = int(upto)
503 22dab079 Antony Chazapis
                if offset > upto:
504 22dab079 Antony Chazapis
                    return None
505 22dab079 Antony Chazapis
                ret.append((offset, upto - offset + 1))
506 22dab079 Antony Chazapis
            else:
507 22dab079 Antony Chazapis
                ret.append((offset, size - offset))
508 b956618e Antony Chazapis
        else:
509 22dab079 Antony Chazapis
            length = int(upto)
510 22dab079 Antony Chazapis
            ret.append((size - length, length))
511 2715ade4 Sofia Papagiannaki
512 22dab079 Antony Chazapis
    return ret
513 22dab079 Antony Chazapis
514 2715ade4 Sofia Papagiannaki
515 22dab079 Antony Chazapis
def get_content_range(request):
516 58a6c894 Antony Chazapis
    """Parse a Content-Range header from the request.
517 2715ade4 Sofia Papagiannaki

518 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
519 22dab079 Antony Chazapis
    or an (offset, length, total) tuple - check as length, total may be None.
520 22dab079 Antony Chazapis
    Returns (None, None, None) if the provided range is '*/*'.
521 22dab079 Antony Chazapis
    """
522 2715ade4 Sofia Papagiannaki
523 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_CONTENT_RANGE', '')
524 22dab079 Antony Chazapis
    if not ranges:
525 22dab079 Antony Chazapis
        return None
526 2715ade4 Sofia Papagiannaki
527 22dab079 Antony Chazapis
    p = re.compile('^bytes (?P<offset>\d+)-(?P<upto>\d*)/(?P<total>(\d+|\*))$')
528 22dab079 Antony Chazapis
    m = p.match(ranges)
529 22dab079 Antony Chazapis
    if not m:
530 22dab079 Antony Chazapis
        if ranges == 'bytes */*':
531 22dab079 Antony Chazapis
            return (None, None, None)
532 22dab079 Antony Chazapis
        return None
533 22dab079 Antony Chazapis
    offset = int(m.group('offset'))
534 22dab079 Antony Chazapis
    upto = m.group('upto')
535 22dab079 Antony Chazapis
    total = m.group('total')
536 22dab079 Antony Chazapis
    if upto != '':
537 22dab079 Antony Chazapis
        upto = int(upto)
538 b956618e Antony Chazapis
    else:
539 22dab079 Antony Chazapis
        upto = None
540 22dab079 Antony Chazapis
    if total != '*':
541 22dab079 Antony Chazapis
        total = int(total)
542 22dab079 Antony Chazapis
    else:
543 22dab079 Antony Chazapis
        total = None
544 70e526a0 Antony Chazapis
    if (upto is not None and offset > upto) or \
545 70e526a0 Antony Chazapis
        (total is not None and offset >= total) or \
546 2715ade4 Sofia Papagiannaki
            (total is not None and upto is not None and upto >= total):
547 22dab079 Antony Chazapis
        return None
548 2715ade4 Sofia Papagiannaki
549 70e526a0 Antony Chazapis
    if upto is None:
550 22dab079 Antony Chazapis
        length = None
551 22dab079 Antony Chazapis
    else:
552 22dab079 Antony Chazapis
        length = upto - offset + 1
553 22dab079 Antony Chazapis
    return (offset, length, total)
554 b956618e Antony Chazapis
555 2715ade4 Sofia Papagiannaki
556 3436eeb0 Antony Chazapis
def get_sharing(request):
557 3436eeb0 Antony Chazapis
    """Parse an X-Object-Sharing header from the request.
558 2715ade4 Sofia Papagiannaki

559 3436eeb0 Antony Chazapis
    Raises BadRequest on error.
560 3436eeb0 Antony Chazapis
    """
561 2715ade4 Sofia Papagiannaki
562 3436eeb0 Antony Chazapis
    permissions = request.META.get('HTTP_X_OBJECT_SHARING')
563 cca6c617 Antony Chazapis
    if permissions is None:
564 3436eeb0 Antony Chazapis
        return None
565 2715ade4 Sofia Papagiannaki
566 0a7f1671 Antony Chazapis
    # TODO: Document or remove '~' replacing.
567 0a7f1671 Antony Chazapis
    permissions = permissions.replace('~', '')
568 2715ade4 Sofia Papagiannaki
569 3436eeb0 Antony Chazapis
    ret = {}
570 cca6c617 Antony Chazapis
    permissions = permissions.replace(' ', '')
571 cca6c617 Antony Chazapis
    if permissions == '':
572 cca6c617 Antony Chazapis
        return ret
573 cca6c617 Antony Chazapis
    for perm in (x for x in permissions.split(';')):
574 cca6c617 Antony Chazapis
        if perm.startswith('read='):
575 2715ade4 Sofia Papagiannaki
            ret['read'] = list(set(
576 2715ade4 Sofia Papagiannaki
                [v.replace(' ', '').lower() for v in perm[5:].split(',')]))
577 02c0c3fa Antony Chazapis
            if '' in ret['read']:
578 02c0c3fa Antony Chazapis
                ret['read'].remove('')
579 e8886082 Antony Chazapis
            if '*' in ret['read']:
580 e8886082 Antony Chazapis
                ret['read'] = ['*']
581 3436eeb0 Antony Chazapis
            if len(ret['read']) == 0:
582 32454501 Sofia Papagiannaki
                raise BadRequest(
583 32454501 Sofia Papagiannaki
                    'Bad X-Object-Sharing header value: invalid length')
584 3436eeb0 Antony Chazapis
        elif perm.startswith('write='):
585 2715ade4 Sofia Papagiannaki
            ret['write'] = list(set(
586 2715ade4 Sofia Papagiannaki
                [v.replace(' ', '').lower() for v in perm[6:].split(',')]))
587 02c0c3fa Antony Chazapis
            if '' in ret['write']:
588 02c0c3fa Antony Chazapis
                ret['write'].remove('')
589 e8886082 Antony Chazapis
            if '*' in ret['write']:
590 e8886082 Antony Chazapis
                ret['write'] = ['*']
591 3436eeb0 Antony Chazapis
            if len(ret['write']) == 0:
592 32454501 Sofia Papagiannaki
                raise BadRequest(
593 32454501 Sofia Papagiannaki
                    'Bad X-Object-Sharing header value: invalid length')
594 3436eeb0 Antony Chazapis
        else:
595 32454501 Sofia Papagiannaki
            raise BadRequest(
596 32454501 Sofia Papagiannaki
                'Bad X-Object-Sharing header value: missing prefix')
597 32454501 Sofia Papagiannaki
598 32454501 Sofia Papagiannaki
    # replace username with uuid
599 32454501 Sofia Papagiannaki
    try:
600 32454501 Sofia Papagiannaki
        ret['read'] = \
601 32454501 Sofia Papagiannaki
            [replace_permissions_username(x) for x in ret.get('read', [])]
602 32454501 Sofia Papagiannaki
        ret['write'] = \
603 32454501 Sofia Papagiannaki
            [replace_permissions_username(x) for x in ret.get('write', [])]
604 874cddc7 Sofia Papagiannaki
    except ItemNotExists, e:
605 32454501 Sofia Papagiannaki
        raise BadRequest(
606 32454501 Sofia Papagiannaki
            'Bad X-Object-Sharing header value: unknown account: %s' % e)
607 2715ade4 Sofia Papagiannaki
608 21a8a6ff Antony Chazapis
    # Keep duplicates only in write list.
609 2715ade4 Sofia Papagiannaki
    dups = [x for x in ret.get(
610 2715ade4 Sofia Papagiannaki
        'read', []) if x in ret.get('write', []) and x != '*']
611 21a8a6ff Antony Chazapis
    if dups:
612 21a8a6ff Antony Chazapis
        for x in dups:
613 21a8a6ff Antony Chazapis
            ret['read'].remove(x)
614 21a8a6ff Antony Chazapis
        if len(ret['read']) == 0:
615 21a8a6ff Antony Chazapis
            del(ret['read'])
616 2715ade4 Sofia Papagiannaki
617 3436eeb0 Antony Chazapis
    return ret
618 3436eeb0 Antony Chazapis
619 2715ade4 Sofia Papagiannaki
620 3ab38c43 Antony Chazapis
def get_public(request):
621 3ab38c43 Antony Chazapis
    """Parse an X-Object-Public header from the request.
622 2715ade4 Sofia Papagiannaki

623 3ab38c43 Antony Chazapis
    Raises BadRequest on error.
624 3ab38c43 Antony Chazapis
    """
625 2715ade4 Sofia Papagiannaki
626 3ab38c43 Antony Chazapis
    public = request.META.get('HTTP_X_OBJECT_PUBLIC')
627 3ab38c43 Antony Chazapis
    if public is None:
628 3ab38c43 Antony Chazapis
        return None
629 2715ade4 Sofia Papagiannaki
630 3ab38c43 Antony Chazapis
    public = public.replace(' ', '').lower()
631 3ab38c43 Antony Chazapis
    if public == 'true':
632 3ab38c43 Antony Chazapis
        return True
633 3ab38c43 Antony Chazapis
    elif public == 'false' or public == '':
634 3ab38c43 Antony Chazapis
        return False
635 3ab38c43 Antony Chazapis
    raise BadRequest('Bad X-Object-Public header value')
636 3ab38c43 Antony Chazapis
637 2715ade4 Sofia Papagiannaki
638 b956618e Antony Chazapis
def raw_input_socket(request):
639 58a6c894 Antony Chazapis
    """Return the socket for reading the rest of the request."""
640 2715ade4 Sofia Papagiannaki
641 b956618e Antony Chazapis
    server_software = request.META.get('SERVER_SOFTWARE')
642 fc1b2a75 Antony Chazapis
    if server_software and server_software.startswith('mod_python'):
643 b956618e Antony Chazapis
        return request._req
644 fc1b2a75 Antony Chazapis
    if 'wsgi.input' in request.environ:
645 fc1b2a75 Antony Chazapis
        return request.environ['wsgi.input']
646 08de868d Antony Chazapis
    raise NotImplemented('Unknown server software')
647 b956618e Antony Chazapis
648 2715ade4 Sofia Papagiannaki
MAX_UPLOAD_SIZE = 5 * (1024 * 1024 * 1024)  # 5GB
649 2715ade4 Sofia Papagiannaki
650 b956618e Antony Chazapis
651 c032f34d Antony Chazapis
def socket_read_iterator(request, length=0, blocksize=4096):
652 58a6c894 Antony Chazapis
    """Return a maximum of blocksize data read from the socket in each iteration.
653 2715ade4 Sofia Papagiannaki

654 22dab079 Antony Chazapis
    Read up to 'length'. If 'length' is negative, will attempt a chunked read.
655 b956618e Antony Chazapis
    The maximum ammount of data read is controlled by MAX_UPLOAD_SIZE.
656 b956618e Antony Chazapis
    """
657 2715ade4 Sofia Papagiannaki
658 c032f34d Antony Chazapis
    sock = raw_input_socket(request)
659 2715ade4 Sofia Papagiannaki
    if length < 0:  # Chunked transfers
660 c032f34d Antony Chazapis
        # Small version (server does the dechunking).
661 032dc768 Antony Chazapis
        if request.environ.get('mod_wsgi.input_chunked', None) or request.META['SERVER_SOFTWARE'].startswith('gunicorn'):
662 c032f34d Antony Chazapis
            while length < MAX_UPLOAD_SIZE:
663 c032f34d Antony Chazapis
                data = sock.read(blocksize)
664 c032f34d Antony Chazapis
                if data == '':
665 c032f34d Antony Chazapis
                    return
666 c032f34d Antony Chazapis
                yield data
667 c032f34d Antony Chazapis
            raise BadRequest('Maximum size is reached')
668 2715ade4 Sofia Papagiannaki
669 c032f34d Antony Chazapis
        # Long version (do the dechunking).
670 22dab079 Antony Chazapis
        data = ''
671 b956618e Antony Chazapis
        while length < MAX_UPLOAD_SIZE:
672 22dab079 Antony Chazapis
            # Get chunk size.
673 22dab079 Antony Chazapis
            if hasattr(sock, 'readline'):
674 22dab079 Antony Chazapis
                chunk_length = sock.readline()
675 22dab079 Antony Chazapis
            else:
676 22dab079 Antony Chazapis
                chunk_length = ''
677 22dab079 Antony Chazapis
                while chunk_length[-1:] != '\n':
678 22dab079 Antony Chazapis
                    chunk_length += sock.read(1)
679 22dab079 Antony Chazapis
                chunk_length.strip()
680 b956618e Antony Chazapis
            pos = chunk_length.find(';')
681 b956618e Antony Chazapis
            if pos >= 0:
682 b956618e Antony Chazapis
                chunk_length = chunk_length[:pos]
683 b956618e Antony Chazapis
            try:
684 b956618e Antony Chazapis
                chunk_length = int(chunk_length, 16)
685 b956618e Antony Chazapis
            except Exception, e:
686 2715ade4 Sofia Papagiannaki
                raise BadRequest('Bad chunk size')
687 2715ade4 Sofia Papagiannaki
                                 # TODO: Change to something more appropriate.
688 22dab079 Antony Chazapis
            # Check if done.
689 b956618e Antony Chazapis
            if chunk_length == 0:
690 22dab079 Antony Chazapis
                if len(data) > 0:
691 22dab079 Antony Chazapis
                    yield data
692 b956618e Antony Chazapis
                return
693 22dab079 Antony Chazapis
            # Get the actual data.
694 b956618e Antony Chazapis
            while chunk_length > 0:
695 22dab079 Antony Chazapis
                chunk = sock.read(min(chunk_length, blocksize))
696 22dab079 Antony Chazapis
                chunk_length -= len(chunk)
697 623a0cf4 Sofia Papagiannaki
                if length > 0:
698 623a0cf4 Sofia Papagiannaki
                    length += len(chunk)
699 22dab079 Antony Chazapis
                data += chunk
700 22dab079 Antony Chazapis
                if len(data) >= blocksize:
701 22dab079 Antony Chazapis
                    ret = data[:blocksize]
702 22dab079 Antony Chazapis
                    data = data[blocksize:]
703 22dab079 Antony Chazapis
                    yield ret
704 2715ade4 Sofia Papagiannaki
            sock.read(2)  # CRLF
705 32a437b1 Sofia Papagiannaki
        raise BadRequest('Maximum size is reached')
706 b956618e Antony Chazapis
    else:
707 b956618e Antony Chazapis
        if length > MAX_UPLOAD_SIZE:
708 32a437b1 Sofia Papagiannaki
            raise BadRequest('Maximum size is reached')
709 b956618e Antony Chazapis
        while length > 0:
710 b956618e Antony Chazapis
            data = sock.read(min(length, blocksize))
711 7b25e082 Antony Chazapis
            if not data:
712 7b25e082 Antony Chazapis
                raise BadRequest()
713 b956618e Antony Chazapis
            length -= len(data)
714 b956618e Antony Chazapis
            yield data
715 b956618e Antony Chazapis
716 2715ade4 Sofia Papagiannaki
717 817890f2 Antony Chazapis
class SaveToBackendHandler(FileUploadHandler):
718 817890f2 Antony Chazapis
    """Handle a file from an HTML form the django way."""
719 2715ade4 Sofia Papagiannaki
720 817890f2 Antony Chazapis
    def __init__(self, request=None):
721 817890f2 Antony Chazapis
        super(SaveToBackendHandler, self).__init__(request)
722 817890f2 Antony Chazapis
        self.backend = request.backend
723 2715ade4 Sofia Papagiannaki
724 817890f2 Antony Chazapis
    def put_data(self, length):
725 817890f2 Antony Chazapis
        if len(self.data) >= length:
726 817890f2 Antony Chazapis
            block = self.data[:length]
727 817890f2 Antony Chazapis
            self.file.hashmap.append(self.backend.put_block(block))
728 817890f2 Antony Chazapis
            self.md5.update(block)
729 817890f2 Antony Chazapis
            self.data = self.data[length:]
730 2715ade4 Sofia Papagiannaki
731 817890f2 Antony Chazapis
    def new_file(self, field_name, file_name, content_type, content_length, charset=None):
732 2715ade4 Sofia Papagiannaki
        self.md5 = hashlib.md5()
733 817890f2 Antony Chazapis
        self.data = ''
734 2715ade4 Sofia Papagiannaki
        self.file = UploadedFile(
735 2715ade4 Sofia Papagiannaki
            name=file_name, content_type=content_type, charset=charset)
736 817890f2 Antony Chazapis
        self.file.size = 0
737 817890f2 Antony Chazapis
        self.file.hashmap = []
738 2715ade4 Sofia Papagiannaki
739 817890f2 Antony Chazapis
    def receive_data_chunk(self, raw_data, start):
740 817890f2 Antony Chazapis
        self.data += raw_data
741 817890f2 Antony Chazapis
        self.file.size += len(raw_data)
742 817890f2 Antony Chazapis
        self.put_data(self.request.backend.block_size)
743 817890f2 Antony Chazapis
        return None
744 2715ade4 Sofia Papagiannaki
745 817890f2 Antony Chazapis
    def file_complete(self, file_size):
746 817890f2 Antony Chazapis
        l = len(self.data)
747 817890f2 Antony Chazapis
        if l > 0:
748 817890f2 Antony Chazapis
            self.put_data(l)
749 817890f2 Antony Chazapis
        self.file.etag = self.md5.hexdigest().lower()
750 817890f2 Antony Chazapis
        return self.file
751 817890f2 Antony Chazapis
752 2715ade4 Sofia Papagiannaki
753 22dab079 Antony Chazapis
class ObjectWrapper(object):
754 58a6c894 Antony Chazapis
    """Return the object's data block-per-block in each iteration.
755 2715ade4 Sofia Papagiannaki

756 22dab079 Antony Chazapis
    Read from the object using the offset and length provided in each entry of the range list.
757 22dab079 Antony Chazapis
    """
758 2715ade4 Sofia Papagiannaki
759 39593b2b Giorgos Verigakis
    def __init__(self, backend, ranges, sizes, hashmaps, boundary):
760 39593b2b Giorgos Verigakis
        self.backend = backend
761 22dab079 Antony Chazapis
        self.ranges = ranges
762 8cb45c13 Antony Chazapis
        self.sizes = sizes
763 8cb45c13 Antony Chazapis
        self.hashmaps = hashmaps
764 22dab079 Antony Chazapis
        self.boundary = boundary
765 8cb45c13 Antony Chazapis
        self.size = sum(self.sizes)
766 2715ade4 Sofia Papagiannaki
767 8cb45c13 Antony Chazapis
        self.file_index = 0
768 8cb45c13 Antony Chazapis
        self.block_index = 0
769 8cb45c13 Antony Chazapis
        self.block_hash = -1
770 22dab079 Antony Chazapis
        self.block = ''
771 2715ade4 Sofia Papagiannaki
772 22dab079 Antony Chazapis
        self.range_index = -1
773 22dab079 Antony Chazapis
        self.offset, self.length = self.ranges[0]
774 2715ade4 Sofia Papagiannaki
775 22dab079 Antony Chazapis
    def __iter__(self):
776 22dab079 Antony Chazapis
        return self
777 2715ade4 Sofia Papagiannaki
778 22dab079 Antony Chazapis
    def part_iterator(self):
779 22dab079 Antony Chazapis
        if self.length > 0:
780 8cb45c13 Antony Chazapis
            # Get the file for the current offset.
781 8cb45c13 Antony Chazapis
            file_size = self.sizes[self.file_index]
782 8cb45c13 Antony Chazapis
            while self.offset >= file_size:
783 8cb45c13 Antony Chazapis
                self.offset -= file_size
784 8cb45c13 Antony Chazapis
                self.file_index += 1
785 8cb45c13 Antony Chazapis
                file_size = self.sizes[self.file_index]
786 2715ade4 Sofia Papagiannaki
787 8cb45c13 Antony Chazapis
            # Get the block for the current position.
788 39593b2b Giorgos Verigakis
            self.block_index = int(self.offset / self.backend.block_size)
789 8cb45c13 Antony Chazapis
            if self.block_hash != self.hashmaps[self.file_index][self.block_index]:
790 2715ade4 Sofia Papagiannaki
                self.block_hash = self.hashmaps[
791 2715ade4 Sofia Papagiannaki
                    self.file_index][self.block_index]
792 22dab079 Antony Chazapis
                try:
793 39593b2b Giorgos Verigakis
                    self.block = self.backend.get_block(self.block_hash)
794 7efc9f86 Sofia Papagiannaki
                except ItemNotExists:
795 22dab079 Antony Chazapis
                    raise ItemNotFound('Block does not exist')
796 2715ade4 Sofia Papagiannaki
797 22dab079 Antony Chazapis
            # Get the data from the block.
798 39593b2b Giorgos Verigakis
            bo = self.offset % self.backend.block_size
799 c9865fe1 Antony Chazapis
            bs = self.backend.block_size
800 45cf0bc8 Antony Chazapis
            if (self.block_index == len(self.hashmaps[self.file_index]) - 1 and
801 2715ade4 Sofia Papagiannaki
                    self.sizes[self.file_index] % self.backend.block_size):
802 c9865fe1 Antony Chazapis
                bs = self.sizes[self.file_index] % self.backend.block_size
803 c9865fe1 Antony Chazapis
            bl = min(self.length, bs - bo)
804 22dab079 Antony Chazapis
            data = self.block[bo:bo + bl]
805 22dab079 Antony Chazapis
            self.offset += bl
806 22dab079 Antony Chazapis
            self.length -= bl
807 22dab079 Antony Chazapis
            return data
808 22dab079 Antony Chazapis
        else:
809 22dab079 Antony Chazapis
            raise StopIteration
810 2715ade4 Sofia Papagiannaki
811 22dab079 Antony Chazapis
    def next(self):
812 22dab079 Antony Chazapis
        if len(self.ranges) == 1:
813 22dab079 Antony Chazapis
            return self.part_iterator()
814 22dab079 Antony Chazapis
        if self.range_index == len(self.ranges):
815 22dab079 Antony Chazapis
            raise StopIteration
816 22dab079 Antony Chazapis
        try:
817 22dab079 Antony Chazapis
            if self.range_index == -1:
818 22dab079 Antony Chazapis
                raise StopIteration
819 22dab079 Antony Chazapis
            return self.part_iterator()
820 22dab079 Antony Chazapis
        except StopIteration:
821 22dab079 Antony Chazapis
            self.range_index += 1
822 22dab079 Antony Chazapis
            out = []
823 22dab079 Antony Chazapis
            if self.range_index < len(self.ranges):
824 22dab079 Antony Chazapis
                # Part header.
825 22dab079 Antony Chazapis
                self.offset, self.length = self.ranges[self.range_index]
826 8cb45c13 Antony Chazapis
                self.file_index = 0
827 22dab079 Antony Chazapis
                if self.range_index > 0:
828 22dab079 Antony Chazapis
                    out.append('')
829 22dab079 Antony Chazapis
                out.append('--' + self.boundary)
830 2715ade4 Sofia Papagiannaki
                out.append('Content-Range: bytes %d-%d/%d' % (
831 2715ade4 Sofia Papagiannaki
                    self.offset, self.offset + self.length - 1, self.size))
832 22dab079 Antony Chazapis
                out.append('Content-Transfer-Encoding: binary')
833 22dab079 Antony Chazapis
                out.append('')
834 22dab079 Antony Chazapis
                out.append('')
835 22dab079 Antony Chazapis
                return '\r\n'.join(out)
836 22dab079 Antony Chazapis
            else:
837 22dab079 Antony Chazapis
                # Footer.
838 22dab079 Antony Chazapis
                out.append('')
839 22dab079 Antony Chazapis
                out.append('--' + self.boundary + '--')
840 22dab079 Antony Chazapis
                out.append('')
841 22dab079 Antony Chazapis
                return '\r\n'.join(out)
842 22dab079 Antony Chazapis
843 2715ade4 Sofia Papagiannaki
844 8cb45c13 Antony Chazapis
def object_data_response(request, sizes, hashmaps, meta, public=False):
845 7bef5750 Antony Chazapis
    """Get the HttpResponse object for replying with the object's data."""
846 2715ade4 Sofia Papagiannaki
847 7bef5750 Antony Chazapis
    # Range handling.
848 8cb45c13 Antony Chazapis
    size = sum(sizes)
849 7bef5750 Antony Chazapis
    ranges = get_range(request, size)
850 7bef5750 Antony Chazapis
    if ranges is None:
851 7bef5750 Antony Chazapis
        ranges = [(0, size)]
852 7bef5750 Antony Chazapis
        ret = 200
853 7bef5750 Antony Chazapis
    else:
854 7bef5750 Antony Chazapis
        check = [True for offset, length in ranges if
855 2715ade4 Sofia Papagiannaki
                 length <= 0 or length > size or
856 2715ade4 Sofia Papagiannaki
                 offset < 0 or offset >= size or
857 2715ade4 Sofia Papagiannaki
                 offset + length > size]
858 7bef5750 Antony Chazapis
        if len(check) > 0:
859 6d1e6dce Sofia Papagiannaki
            raise RangeNotSatisfiable('Requested range exceeds object limits')
860 7bef5750 Antony Chazapis
        ret = 206
861 15d465b8 Antony Chazapis
        if_range = request.META.get('HTTP_IF_RANGE')
862 15d465b8 Antony Chazapis
        if if_range:
863 6d1e6dce Sofia Papagiannaki
            try:
864 15d465b8 Antony Chazapis
                # Modification time has passed instead.
865 6d1e6dce Sofia Papagiannaki
                last_modified = parse_http_date(if_range)
866 6d1e6dce Sofia Papagiannaki
                if last_modified != meta['modified']:
867 6d1e6dce Sofia Papagiannaki
                    ranges = [(0, size)]
868 6d1e6dce Sofia Papagiannaki
                    ret = 200
869 6d1e6dce Sofia Papagiannaki
            except ValueError:
870 33b4e4a6 Antony Chazapis
                if if_range != meta['checksum']:
871 6d1e6dce Sofia Papagiannaki
                    ranges = [(0, size)]
872 6d1e6dce Sofia Papagiannaki
                    ret = 200
873 2715ade4 Sofia Papagiannaki
874 7bef5750 Antony Chazapis
    if ret == 206 and len(ranges) > 1:
875 7bef5750 Antony Chazapis
        boundary = uuid.uuid4().hex
876 7bef5750 Antony Chazapis
    else:
877 7bef5750 Antony Chazapis
        boundary = ''
878 39593b2b Giorgos Verigakis
    wrapper = ObjectWrapper(request.backend, ranges, sizes, hashmaps, boundary)
879 7bef5750 Antony Chazapis
    response = HttpResponse(wrapper, status=ret)
880 02c0c3fa Antony Chazapis
    put_object_headers(response, meta, public)
881 7bef5750 Antony Chazapis
    if ret == 206:
882 7bef5750 Antony Chazapis
        if len(ranges) == 1:
883 7bef5750 Antony Chazapis
            offset, length = ranges[0]
884 2715ade4 Sofia Papagiannaki
            response[
885 2715ade4 Sofia Papagiannaki
                'Content-Length'] = length  # Update with the correct length.
886 2715ade4 Sofia Papagiannaki
            response['Content-Range'] = 'bytes %d-%d/%d' % (
887 2715ade4 Sofia Papagiannaki
                offset, offset + length - 1, size)
888 7bef5750 Antony Chazapis
        else:
889 7bef5750 Antony Chazapis
            del(response['Content-Length'])
890 2715ade4 Sofia Papagiannaki
            response['Content-Type'] = 'multipart/byteranges; boundary=%s' % (
891 2715ade4 Sofia Papagiannaki
                boundary,)
892 7bef5750 Antony Chazapis
    return response
893 7bef5750 Antony Chazapis
894 2715ade4 Sofia Papagiannaki
895 39593b2b Giorgos Verigakis
def put_object_block(request, hashmap, data, offset):
896 cb146cf9 Antony Chazapis
    """Put one block of data at the given offset."""
897 2715ade4 Sofia Papagiannaki
898 39593b2b Giorgos Verigakis
    bi = int(offset / request.backend.block_size)
899 39593b2b Giorgos Verigakis
    bo = offset % request.backend.block_size
900 39593b2b Giorgos Verigakis
    bl = min(len(data), request.backend.block_size - bo)
901 cb146cf9 Antony Chazapis
    if bi < len(hashmap):
902 39593b2b Giorgos Verigakis
        hashmap[bi] = request.backend.update_block(hashmap[bi], data[:bl], bo)
903 cb146cf9 Antony Chazapis
    else:
904 39593b2b Giorgos Verigakis
        hashmap.append(request.backend.put_block(('\x00' * bo) + data[:bl]))
905 2715ade4 Sofia Papagiannaki
    return bl  # Return ammount of data written.
906 2715ade4 Sofia Papagiannaki
907 cb146cf9 Antony Chazapis
908 b3155065 Antony Chazapis
def hashmap_md5(backend, hashmap, size):
909 cddcf432 chazapis
    """Produce the MD5 sum from the data in the hashmap."""
910 2715ade4 Sofia Papagiannaki
911 cddcf432 chazapis
    # TODO: Search backend for the MD5 of another object with the same hashmap and size...
912 cddcf432 chazapis
    md5 = hashlib.md5()
913 b3155065 Antony Chazapis
    bs = backend.block_size
914 cddcf432 chazapis
    for bi, hash in enumerate(hashmap):
915 2715ade4 Sofia Papagiannaki
        data = backend.get_block(hash)  # Blocks come in padded.
916 cddcf432 chazapis
        if bi == len(hashmap) - 1:
917 c9865fe1 Antony Chazapis
            data = data[:size % bs]
918 c9865fe1 Antony Chazapis
        md5.update(data)
919 cddcf432 chazapis
    return md5.hexdigest().lower()
920 49133392 Antony Chazapis
921 2715ade4 Sofia Papagiannaki
922 af7bb62f Antony Chazapis
def simple_list_response(request, l):
923 6b6b6c1e Antony Chazapis
    if request.serialization == 'text':
924 6b6b6c1e Antony Chazapis
        return '\n'.join(l) + '\n'
925 6b6b6c1e Antony Chazapis
    if request.serialization == 'xml':
926 af7bb62f Antony Chazapis
        return render_to_string('items.xml', {'items': l})
927 6b6b6c1e Antony Chazapis
    if request.serialization == 'json':
928 6b6b6c1e Antony Chazapis
        return json.dumps(l)
929 6b6b6c1e Antony Chazapis
930 35d42381 Vangelis Koukis
931 edb7875c Christos Stavrakakis
from pithos.backends.util import PithosBackendPool
932 35d42381 Vangelis Koukis
POOL_SIZE = 5
933 47462eda Filippos Giannakos
if RADOS_STORAGE:
934 47462eda Filippos Giannakos
    BLOCK_PARAMS = { 'mappool': RADOS_POOL_MAPS,
935 47462eda Filippos Giannakos
                     'blockpool': RADOS_POOL_BLOCKS,
936 47462eda Filippos Giannakos
                   }
937 47462eda Filippos Giannakos
else:
938 47462eda Filippos Giannakos
    BLOCK_PARAMS = { 'mappool': None,
939 47462eda Filippos Giannakos
                     'blockpool': None,
940 47462eda Filippos Giannakos
                   }
941 35d42381 Vangelis Koukis
942 761c2b3c root
943 edb7875c Christos Stavrakakis
_pithos_backend_pool = PithosBackendPool(size=POOL_SIZE,
944 edb7875c Christos Stavrakakis
                                         db_module=BACKEND_DB_MODULE,
945 edb7875c Christos Stavrakakis
                                         db_connection=BACKEND_DB_CONNECTION,
946 edb7875c Christos Stavrakakis
                                         block_module=BACKEND_BLOCK_MODULE,
947 edb7875c Christos Stavrakakis
                                         block_path=BACKEND_BLOCK_PATH,
948 edb7875c Christos Stavrakakis
                                         block_umask=BACKEND_BLOCK_UMASK,
949 edb7875c Christos Stavrakakis
                                         queue_module=BACKEND_QUEUE_MODULE,
950 b1dadd0e Sofia Papagiannaki
                                         queue_hosts=BACKEND_QUEUE_HOSTS,
951 b1dadd0e Sofia Papagiannaki
                                         queue_exchange=BACKEND_QUEUE_EXCHANGE,
952 3173699c Sofia Papagiannaki
                                         quotaholder_url=QUOTAHOLDER_URL,
953 3173699c Sofia Papagiannaki
                                         quotaholder_token=QUOTAHOLDER_TOKEN,
954 a9420179 Sofia Papagiannaki
                                         free_versioning=BACKEND_FREE_VERSIONING,
955 47462eda Filippos Giannakos
                                         block_params=BLOCK_PARAMS)
956 35d42381 Vangelis Koukis
957 35d42381 Vangelis Koukis
def get_backend():
958 edb7875c Christos Stavrakakis
    backend = _pithos_backend_pool.pool_get()
959 edb7875c Christos Stavrakakis
    backend.default_policy['quota'] = BACKEND_QUOTA
960 edb7875c Christos Stavrakakis
    backend.default_policy['versioning'] = BACKEND_VERSIONING
961 f77b1da9 Stratos Psomadakis
    backend.messages = []
962 edb7875c Christos Stavrakakis
    return backend
963 35d42381 Vangelis Koukis
964 35d42381 Vangelis Koukis
965 9fefc052 Antony Chazapis
def update_request_headers(request):
966 9fefc052 Antony Chazapis
    # Handle URL-encoded keys and values.
967 2715ade4 Sofia Papagiannaki
    meta = dict([(
968 2715ade4 Sofia Papagiannaki
        k, v) for k, v in request.META.iteritems() if k.startswith('HTTP_')])
969 88283e9e Antony Chazapis
    for k, v in meta.iteritems():
970 88283e9e Antony Chazapis
        try:
971 88283e9e Antony Chazapis
            k.decode('ascii')
972 88283e9e Antony Chazapis
            v.decode('ascii')
973 88283e9e Antony Chazapis
        except UnicodeDecodeError:
974 88283e9e Antony Chazapis
            raise BadRequest('Bad character in headers.')
975 88283e9e Antony Chazapis
        if '%' in k or '%' in v:
976 88283e9e Antony Chazapis
            del(request.META[k])
977 2715ade4 Sofia Papagiannaki
            request.META[unquote(k)] = smart_unicode(unquote(
978 2715ade4 Sofia Papagiannaki
                v), strings_only=True)
979 2715ade4 Sofia Papagiannaki
980 9fefc052 Antony Chazapis
981 b956618e Antony Chazapis
def update_response_headers(request, response):
982 b956618e Antony Chazapis
    if request.serialization == 'xml':
983 b956618e Antony Chazapis
        response['Content-Type'] = 'application/xml; charset=UTF-8'
984 b956618e Antony Chazapis
    elif request.serialization == 'json':
985 b956618e Antony Chazapis
        response['Content-Type'] = 'application/json; charset=UTF-8'
986 22dab079 Antony Chazapis
    elif not response['Content-Type']:
987 b956618e Antony Chazapis
        response['Content-Type'] = 'text/plain; charset=UTF-8'
988 2715ade4 Sofia Papagiannaki
989 9fefc052 Antony Chazapis
    if (not response.has_header('Content-Length') and
990 9fefc052 Antony Chazapis
        not (response.has_header('Content-Type') and
991 9fefc052 Antony Chazapis
             response['Content-Type'].startswith('multipart/byteranges'))):
992 b980169d Antony Chazapis
        response['Content-Length'] = len(response.content)
993 2715ade4 Sofia Papagiannaki
994 9fefc052 Antony Chazapis
    # URL-encode unicode in headers.
995 9fefc052 Antony Chazapis
    meta = response.items()
996 9fefc052 Antony Chazapis
    for k, v in meta:
997 88283e9e Antony Chazapis
        if (k.startswith('X-Account-') or k.startswith('X-Container-') or
998 2715ade4 Sofia Papagiannaki
                k.startswith('X-Object-') or k.startswith('Content-')):
999 9fefc052 Antony Chazapis
            del(response[k])
1000 88283e9e Antony Chazapis
            response[quote(k)] = quote(v, safe='/=,:@; ')
1001 b956618e Antony Chazapis
1002 2715ade4 Sofia Papagiannaki
1003 b956618e Antony Chazapis
def render_fault(request, fault):
1004 a7dff008 Antony Chazapis
    if isinstance(fault, InternalServerError) and settings.DEBUG:
1005 b956618e Antony Chazapis
        fault.details = format_exc(fault)
1006 2715ade4 Sofia Papagiannaki
1007 b956618e Antony Chazapis
    request.serialization = 'text'
1008 08de868d Antony Chazapis
    data = fault.message + '\n'
1009 08de868d Antony Chazapis
    if fault.details:
1010 08de868d Antony Chazapis
        data += '\n' + fault.details
1011 b956618e Antony Chazapis
    response = HttpResponse(data, status=fault.code)
1012 b956618e Antony Chazapis
    update_response_headers(request, response)
1013 b956618e Antony Chazapis
    return response
1014 b956618e Antony Chazapis
1015 2715ade4 Sofia Papagiannaki
1016 b956618e Antony Chazapis
def request_serialization(request, format_allowed=False):
1017 58a6c894 Antony Chazapis
    """Return the serialization format requested.
1018 2715ade4 Sofia Papagiannaki

1019 b956618e Antony Chazapis
    Valid formats are 'text' and 'json', 'xml' if 'format_allowed' is True.
1020 b956618e Antony Chazapis
    """
1021 2715ade4 Sofia Papagiannaki
1022 b956618e Antony Chazapis
    if not format_allowed:
1023 b956618e Antony Chazapis
        return 'text'
1024 2715ade4 Sofia Papagiannaki
1025 b956618e Antony Chazapis
    format = request.GET.get('format')
1026 b956618e Antony Chazapis
    if format == 'json':
1027 b956618e Antony Chazapis
        return 'json'
1028 b956618e Antony Chazapis
    elif format == 'xml':
1029 b956618e Antony Chazapis
        return 'xml'
1030 2715ade4 Sofia Papagiannaki
1031 f9f15f92 Antony Chazapis
    for item in request.META.get('HTTP_ACCEPT', '').split(','):
1032 f9f15f92 Antony Chazapis
        accept, sep, rest = item.strip().partition(';')
1033 f9f15f92 Antony Chazapis
        if accept == 'application/json':
1034 f9f15f92 Antony Chazapis
            return 'json'
1035 f9f15f92 Antony Chazapis
        elif accept == 'application/xml' or accept == 'text/xml':
1036 f9f15f92 Antony Chazapis
            return 'xml'
1037 2715ade4 Sofia Papagiannaki
1038 b956618e Antony Chazapis
    return 'text'
1039 b956618e Antony Chazapis
1040 b3102a96 Sofia Papagiannaki
class User(unicode):
1041 d62295ba Sofia Papagiannaki
    pass
1042 35d42381 Vangelis Koukis
1043 c846fad1 Sofia Papagiannaki
def get_pithos_usage(usage):
1044 c846fad1 Sofia Papagiannaki
    for u in usage:
1045 c846fad1 Sofia Papagiannaki
        if u.get('name') == 'pithos+.diskspace':
1046 c846fad1 Sofia Papagiannaki
            return u
1047 c846fad1 Sofia Papagiannaki
1048 c846fad1 Sofia Papagiannaki
def api_method(http_method=None, format_allowed=False, user_required=True,
1049 c846fad1 Sofia Papagiannaki
        request_usage=False):
1050 58a6c894 Antony Chazapis
    """Decorator function for views that implement an API method."""
1051 2715ade4 Sofia Papagiannaki
1052 b956618e Antony Chazapis
    def decorator(func):
1053 b956618e Antony Chazapis
        @wraps(func)
1054 b956618e Antony Chazapis
        def wrapper(request, *args, **kwargs):
1055 b956618e Antony Chazapis
            try:
1056 b956618e Antony Chazapis
                if http_method and request.method != http_method:
1057 b956618e Antony Chazapis
                    raise BadRequest('Method not allowed.')
1058 2715ade4 Sofia Papagiannaki
1059 dfdf4802 Sofia Papagiannaki
                if user_required:
1060 dfdf4802 Sofia Papagiannaki
                    token = None
1061 dfdf4802 Sofia Papagiannaki
                    if request.method in ('HEAD', 'GET') and COOKIE_NAME in request.COOKIES:
1062 2715ade4 Sofia Papagiannaki
                        cookie_value = unquote(
1063 2715ade4 Sofia Papagiannaki
                            request.COOKIES.get(COOKIE_NAME, ''))
1064 bf45cb4a Sofia Papagiannaki
                        account, sep, token = cookie_value.partition('|')
1065 2715ade4 Sofia Papagiannaki
                    get_user(request,
1066 c846fad1 Sofia Papagiannaki
                             AUTHENTICATION_URL,
1067 c846fad1 Sofia Papagiannaki
                             AUTHENTICATION_USERS,
1068 c846fad1 Sofia Papagiannaki
                             token,
1069 c846fad1 Sofia Papagiannaki
                             user_required)
1070 dfdf4802 Sofia Papagiannaki
                    if  getattr(request, 'user', None) is None:
1071 dfdf4802 Sofia Papagiannaki
                        raise Unauthorized('Access denied')
1072 b3102a96 Sofia Papagiannaki
                    assert getattr(request, 'user_uniq', None) != None
1073 b3102a96 Sofia Papagiannaki
                    request.user_uniq = User(request.user_uniq)
1074 019d9e19 Sofia Papagiannaki
                    request.user_uniq.uuid = request.user.get('uuid')
1075 c846fad1 Sofia Papagiannaki
                    request.user_usage = get_pithos_usage(
1076 c846fad1 Sofia Papagiannaki
                        request.user.get('usage'))
1077 2c22e4ac Antony Chazapis
                
1078 b956618e Antony Chazapis
                # The args variable may contain up to (account, container, object).
1079 b956618e Antony Chazapis
                if len(args) > 1 and len(args[1]) > 256:
1080 b956618e Antony Chazapis
                    raise BadRequest('Container name too large.')
1081 b956618e Antony Chazapis
                if len(args) > 2 and len(args[2]) > 1024:
1082 b956618e Antony Chazapis
                    raise BadRequest('Object name too large.')
1083 2715ade4 Sofia Papagiannaki
1084 9fefc052 Antony Chazapis
                # Format and check headers.
1085 9fefc052 Antony Chazapis
                update_request_headers(request)
1086 2715ade4 Sofia Papagiannaki
1087 b956618e Antony Chazapis
                # Fill in custom request variables.
1088 2715ade4 Sofia Papagiannaki
                request.serialization = request_serialization(
1089 2715ade4 Sofia Papagiannaki
                    request, format_allowed)
1090 228de81b Antony Chazapis
                request.backend = get_backend()
1091 2715ade4 Sofia Papagiannaki
1092 b956618e Antony Chazapis
                response = func(request, *args, **kwargs)
1093 b956618e Antony Chazapis
                update_response_headers(request, response)
1094 b956618e Antony Chazapis
                return response
1095 b956618e Antony Chazapis
            except Fault, fault:
1096 6e9c8fc1 Christos Stavrakakis
                if fault.code >= 500:
1097 6e9c8fc1 Christos Stavrakakis
                    logger.exception("API Fault")
1098 b956618e Antony Chazapis
                return render_fault(request, fault)
1099 b956618e Antony Chazapis
            except BaseException, e:
1100 b956618e Antony Chazapis
                logger.exception('Unexpected error: %s' % e)
1101 dfdf4802 Sofia Papagiannaki
                fault = InternalServerError('Unexpected error: %s' % e)
1102 b956618e Antony Chazapis
                return render_fault(request, fault)
1103 7b25e082 Antony Chazapis
            finally:
1104 297513ba Antony Chazapis
                if getattr(request, 'backend', None) is not None:
1105 d14fe290 Antony Chazapis
                    request.backend.close()
1106 b956618e Antony Chazapis
        return wrapper
1107 b956618e Antony Chazapis
    return decorator