Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (41 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 a7dff008 Antony Chazapis
from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION,
59 761c2b3c root
                                 BACKEND_BLOCK_MODULE, BACKEND_BLOCK_PATH,
60 761c2b3c root
                                 BACKEND_BLOCK_UMASK,
61 f4fbb0fa Sofia Papagiannaki
                                 BACKEND_QUEUE_MODULE, BACKEND_QUEUE_HOSTS,
62 1f3f907f Sofia Papagiannaki
                                 BACKEND_QUEUE_EXCHANGE, USE_QUOTAHOLDER,
63 3173699c Sofia Papagiannaki
                                 QUOTAHOLDER_URL, QUOTAHOLDER_TOKEN,
64 b336e6fa Georgios D. Tsoukalas
                                 QUOTAHOLDER_POOLSIZE,
65 3173699c Sofia Papagiannaki
                                 BACKEND_QUOTA, BACKEND_VERSIONING,
66 b1dadd0e Sofia Papagiannaki
                                 BACKEND_FREE_VERSIONING,
67 761c2b3c root
                                 AUTHENTICATION_URL, AUTHENTICATION_USERS,
68 27932481 Sofia Papagiannaki
                                 COOKIE_NAME, USER_CATALOG_URL,
69 47462eda Filippos Giannakos
                                 RADOS_STORAGE, RADOS_POOL_BLOCKS,
70 56f3c759 Sofia Papagiannaki
                                 RADOS_POOL_MAPS, TRANSLATE_UUIDS,
71 4a105ce2 Sofia Papagiannaki
                                 PUBLIC_URL_SECURITY,
72 56f3c759 Sofia Papagiannaki
                                 PUBLIC_URL_ALPHABET)
73 39593b2b Giorgos Verigakis
from pithos.backends import connect_backend
74 32454501 Sofia Papagiannaki
from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists,
75 32454501 Sofia Papagiannaki
                                  VersionNotExists)
76 890c2065 Sofia Papagiannaki
from synnefo.lib.astakos import (get_user_uuid, get_displayname,
77 890c2065 Sofia Papagiannaki
                                 get_uuids, get_displaynames)
78 b956618e Antony Chazapis
79 b956618e Antony Chazapis
import logging
80 22dab079 Antony Chazapis
import re
81 cbfb6636 Sofia Papagiannaki
import hashlib
82 7bef5750 Antony Chazapis
import uuid
83 c48acbfd Sofia Papagiannaki
import decimal
84 8c793655 Antony Chazapis
85 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
86 b956618e Antony Chazapis
87 b956618e Antony Chazapis
88 e11087c2 Antony Chazapis
class UTC(tzinfo):
89 2715ade4 Sofia Papagiannaki
    def utcoffset(self, dt):
90 2715ade4 Sofia Papagiannaki
        return timedelta(0)
91 e11087c2 Antony Chazapis
92 2715ade4 Sofia Papagiannaki
    def tzname(self, dt):
93 2715ade4 Sofia Papagiannaki
        return 'UTC'
94 e11087c2 Antony Chazapis
95 2715ade4 Sofia Papagiannaki
    def dst(self, dt):
96 2715ade4 Sofia Papagiannaki
        return timedelta(0)
97 e11087c2 Antony Chazapis
98 e11087c2 Antony Chazapis
99 c48acbfd Sofia Papagiannaki
def json_encode_decimal(obj):
100 c48acbfd Sofia Papagiannaki
    if isinstance(obj, decimal.Decimal):
101 c48acbfd Sofia Papagiannaki
        return str(obj)
102 c48acbfd Sofia Papagiannaki
    raise TypeError(repr(obj) + " is not JSON serializable")
103 c48acbfd Sofia Papagiannaki
104 2715ade4 Sofia Papagiannaki
105 e11087c2 Antony Chazapis
def isoformat(d):
106 2715ade4 Sofia Papagiannaki
    """Return an ISO8601 date string that includes a timezone."""
107 2715ade4 Sofia Papagiannaki
108 2715ade4 Sofia Papagiannaki
    return d.replace(tzinfo=UTC()).isoformat()
109 e11087c2 Antony Chazapis
110 e11087c2 Antony Chazapis
111 804e8fe7 Antony Chazapis
def rename_meta_key(d, old, new):
112 804e8fe7 Antony Chazapis
    if old not in d:
113 804e8fe7 Antony Chazapis
        return
114 804e8fe7 Antony Chazapis
    d[new] = d[old]
115 804e8fe7 Antony Chazapis
    del(d[old])
116 804e8fe7 Antony Chazapis
117 2715ade4 Sofia Papagiannaki
118 02c0c3fa Antony Chazapis
def printable_header_dict(d):
119 b956618e Antony Chazapis
    """Format a meta dictionary for printing out json/xml.
120 2715ade4 Sofia Papagiannaki

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

505 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
506 22dab079 Antony Chazapis
    or a list of (offset, length) tuples - should be further checked.
507 b956618e Antony Chazapis
    """
508 2715ade4 Sofia Papagiannaki
509 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_RANGE', '').replace(' ', '')
510 22dab079 Antony Chazapis
    if not ranges.startswith('bytes='):
511 b956618e Antony Chazapis
        return None
512 2715ade4 Sofia Papagiannaki
513 22dab079 Antony Chazapis
    ret = []
514 22dab079 Antony Chazapis
    for r in (x.strip() for x in ranges[6:].split(',')):
515 22dab079 Antony Chazapis
        p = re.compile('^(?P<offset>\d*)-(?P<upto>\d*)$')
516 22dab079 Antony Chazapis
        m = p.match(r)
517 22dab079 Antony Chazapis
        if not m:
518 22dab079 Antony Chazapis
            return None
519 22dab079 Antony Chazapis
        offset = m.group('offset')
520 22dab079 Antony Chazapis
        upto = m.group('upto')
521 22dab079 Antony Chazapis
        if offset == '' and upto == '':
522 b956618e Antony Chazapis
            return None
523 2715ade4 Sofia Papagiannaki
524 22dab079 Antony Chazapis
        if offset != '':
525 22dab079 Antony Chazapis
            offset = int(offset)
526 22dab079 Antony Chazapis
            if upto != '':
527 b956618e Antony Chazapis
                upto = int(upto)
528 22dab079 Antony Chazapis
                if offset > upto:
529 22dab079 Antony Chazapis
                    return None
530 22dab079 Antony Chazapis
                ret.append((offset, upto - offset + 1))
531 22dab079 Antony Chazapis
            else:
532 22dab079 Antony Chazapis
                ret.append((offset, size - offset))
533 b956618e Antony Chazapis
        else:
534 22dab079 Antony Chazapis
            length = int(upto)
535 22dab079 Antony Chazapis
            ret.append((size - length, length))
536 2715ade4 Sofia Papagiannaki
537 22dab079 Antony Chazapis
    return ret
538 22dab079 Antony Chazapis
539 2715ade4 Sofia Papagiannaki
540 22dab079 Antony Chazapis
def get_content_range(request):
541 58a6c894 Antony Chazapis
    """Parse a Content-Range header from the request.
542 2715ade4 Sofia Papagiannaki

543 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
544 22dab079 Antony Chazapis
    or an (offset, length, total) tuple - check as length, total may be None.
545 22dab079 Antony Chazapis
    Returns (None, None, None) if the provided range is '*/*'.
546 22dab079 Antony Chazapis
    """
547 2715ade4 Sofia Papagiannaki
548 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_CONTENT_RANGE', '')
549 22dab079 Antony Chazapis
    if not ranges:
550 22dab079 Antony Chazapis
        return None
551 2715ade4 Sofia Papagiannaki
552 22dab079 Antony Chazapis
    p = re.compile('^bytes (?P<offset>\d+)-(?P<upto>\d*)/(?P<total>(\d+|\*))$')
553 22dab079 Antony Chazapis
    m = p.match(ranges)
554 22dab079 Antony Chazapis
    if not m:
555 22dab079 Antony Chazapis
        if ranges == 'bytes */*':
556 22dab079 Antony Chazapis
            return (None, None, None)
557 22dab079 Antony Chazapis
        return None
558 22dab079 Antony Chazapis
    offset = int(m.group('offset'))
559 22dab079 Antony Chazapis
    upto = m.group('upto')
560 22dab079 Antony Chazapis
    total = m.group('total')
561 22dab079 Antony Chazapis
    if upto != '':
562 22dab079 Antony Chazapis
        upto = int(upto)
563 b956618e Antony Chazapis
    else:
564 22dab079 Antony Chazapis
        upto = None
565 22dab079 Antony Chazapis
    if total != '*':
566 22dab079 Antony Chazapis
        total = int(total)
567 22dab079 Antony Chazapis
    else:
568 22dab079 Antony Chazapis
        total = None
569 70e526a0 Antony Chazapis
    if (upto is not None and offset > upto) or \
570 70e526a0 Antony Chazapis
        (total is not None and offset >= total) or \
571 2715ade4 Sofia Papagiannaki
            (total is not None and upto is not None and upto >= total):
572 22dab079 Antony Chazapis
        return None
573 2715ade4 Sofia Papagiannaki
574 70e526a0 Antony Chazapis
    if upto is None:
575 22dab079 Antony Chazapis
        length = None
576 22dab079 Antony Chazapis
    else:
577 22dab079 Antony Chazapis
        length = upto - offset + 1
578 22dab079 Antony Chazapis
    return (offset, length, total)
579 b956618e Antony Chazapis
580 2715ade4 Sofia Papagiannaki
581 3436eeb0 Antony Chazapis
def get_sharing(request):
582 3436eeb0 Antony Chazapis
    """Parse an X-Object-Sharing header from the request.
583 2715ade4 Sofia Papagiannaki

584 3436eeb0 Antony Chazapis
    Raises BadRequest on error.
585 3436eeb0 Antony Chazapis
    """
586 2715ade4 Sofia Papagiannaki
587 3436eeb0 Antony Chazapis
    permissions = request.META.get('HTTP_X_OBJECT_SHARING')
588 cca6c617 Antony Chazapis
    if permissions is None:
589 3436eeb0 Antony Chazapis
        return None
590 2715ade4 Sofia Papagiannaki
591 0a7f1671 Antony Chazapis
    # TODO: Document or remove '~' replacing.
592 0a7f1671 Antony Chazapis
    permissions = permissions.replace('~', '')
593 2715ade4 Sofia Papagiannaki
594 3436eeb0 Antony Chazapis
    ret = {}
595 cca6c617 Antony Chazapis
    permissions = permissions.replace(' ', '')
596 cca6c617 Antony Chazapis
    if permissions == '':
597 cca6c617 Antony Chazapis
        return ret
598 cca6c617 Antony Chazapis
    for perm in (x for x in permissions.split(';')):
599 cca6c617 Antony Chazapis
        if perm.startswith('read='):
600 2715ade4 Sofia Papagiannaki
            ret['read'] = list(set(
601 2715ade4 Sofia Papagiannaki
                [v.replace(' ', '').lower() for v in perm[5:].split(',')]))
602 02c0c3fa Antony Chazapis
            if '' in ret['read']:
603 02c0c3fa Antony Chazapis
                ret['read'].remove('')
604 e8886082 Antony Chazapis
            if '*' in ret['read']:
605 e8886082 Antony Chazapis
                ret['read'] = ['*']
606 3436eeb0 Antony Chazapis
            if len(ret['read']) == 0:
607 32454501 Sofia Papagiannaki
                raise BadRequest(
608 32454501 Sofia Papagiannaki
                    'Bad X-Object-Sharing header value: invalid length')
609 3436eeb0 Antony Chazapis
        elif perm.startswith('write='):
610 2715ade4 Sofia Papagiannaki
            ret['write'] = list(set(
611 2715ade4 Sofia Papagiannaki
                [v.replace(' ', '').lower() for v in perm[6:].split(',')]))
612 02c0c3fa Antony Chazapis
            if '' in ret['write']:
613 02c0c3fa Antony Chazapis
                ret['write'].remove('')
614 e8886082 Antony Chazapis
            if '*' in ret['write']:
615 e8886082 Antony Chazapis
                ret['write'] = ['*']
616 3436eeb0 Antony Chazapis
            if len(ret['write']) == 0:
617 32454501 Sofia Papagiannaki
                raise BadRequest(
618 32454501 Sofia Papagiannaki
                    'Bad X-Object-Sharing header value: invalid length')
619 3436eeb0 Antony Chazapis
        else:
620 32454501 Sofia Papagiannaki
            raise BadRequest(
621 32454501 Sofia Papagiannaki
                'Bad X-Object-Sharing header value: missing prefix')
622 32454501 Sofia Papagiannaki
623 890c2065 Sofia Papagiannaki
    # replace displayname with uuid
624 469d0997 Georgios D. Tsoukalas
    if TRANSLATE_UUIDS:
625 469d0997 Georgios D. Tsoukalas
        try:
626 469d0997 Georgios D. Tsoukalas
            ret['read'] = [replace_permissions_displayname(
627 469d0997 Georgios D. Tsoukalas
                    getattr(request, 'token', None), x) \
628 469d0997 Georgios D. Tsoukalas
                        for x in ret.get('read', [])]
629 469d0997 Georgios D. Tsoukalas
            ret['write'] = [replace_permissions_displayname(
630 469d0997 Georgios D. Tsoukalas
                    getattr(request, 'token', None), x) \
631 469d0997 Georgios D. Tsoukalas
                        for x in ret.get('write', [])]
632 469d0997 Georgios D. Tsoukalas
        except ItemNotExists, e:
633 469d0997 Georgios D. Tsoukalas
            raise BadRequest(
634 469d0997 Georgios D. Tsoukalas
                'Bad X-Object-Sharing header value: unknown account: %s' % e)
635 2715ade4 Sofia Papagiannaki
636 21a8a6ff Antony Chazapis
    # Keep duplicates only in write list.
637 2715ade4 Sofia Papagiannaki
    dups = [x for x in ret.get(
638 2715ade4 Sofia Papagiannaki
        'read', []) if x in ret.get('write', []) and x != '*']
639 21a8a6ff Antony Chazapis
    if dups:
640 21a8a6ff Antony Chazapis
        for x in dups:
641 21a8a6ff Antony Chazapis
            ret['read'].remove(x)
642 21a8a6ff Antony Chazapis
        if len(ret['read']) == 0:
643 21a8a6ff Antony Chazapis
            del(ret['read'])
644 2715ade4 Sofia Papagiannaki
645 3436eeb0 Antony Chazapis
    return ret
646 3436eeb0 Antony Chazapis
647 2715ade4 Sofia Papagiannaki
648 3ab38c43 Antony Chazapis
def get_public(request):
649 3ab38c43 Antony Chazapis
    """Parse an X-Object-Public header from the request.
650 2715ade4 Sofia Papagiannaki

651 3ab38c43 Antony Chazapis
    Raises BadRequest on error.
652 3ab38c43 Antony Chazapis
    """
653 2715ade4 Sofia Papagiannaki
654 3ab38c43 Antony Chazapis
    public = request.META.get('HTTP_X_OBJECT_PUBLIC')
655 3ab38c43 Antony Chazapis
    if public is None:
656 3ab38c43 Antony Chazapis
        return None
657 2715ade4 Sofia Papagiannaki
658 3ab38c43 Antony Chazapis
    public = public.replace(' ', '').lower()
659 3ab38c43 Antony Chazapis
    if public == 'true':
660 3ab38c43 Antony Chazapis
        return True
661 3ab38c43 Antony Chazapis
    elif public == 'false' or public == '':
662 3ab38c43 Antony Chazapis
        return False
663 3ab38c43 Antony Chazapis
    raise BadRequest('Bad X-Object-Public header value')
664 3ab38c43 Antony Chazapis
665 2715ade4 Sofia Papagiannaki
666 b956618e Antony Chazapis
def raw_input_socket(request):
667 58a6c894 Antony Chazapis
    """Return the socket for reading the rest of the request."""
668 2715ade4 Sofia Papagiannaki
669 b956618e Antony Chazapis
    server_software = request.META.get('SERVER_SOFTWARE')
670 fc1b2a75 Antony Chazapis
    if server_software and server_software.startswith('mod_python'):
671 b956618e Antony Chazapis
        return request._req
672 fc1b2a75 Antony Chazapis
    if 'wsgi.input' in request.environ:
673 fc1b2a75 Antony Chazapis
        return request.environ['wsgi.input']
674 08de868d Antony Chazapis
    raise NotImplemented('Unknown server software')
675 b956618e Antony Chazapis
676 2715ade4 Sofia Papagiannaki
MAX_UPLOAD_SIZE = 5 * (1024 * 1024 * 1024)  # 5GB
677 2715ade4 Sofia Papagiannaki
678 b956618e Antony Chazapis
679 c032f34d Antony Chazapis
def socket_read_iterator(request, length=0, blocksize=4096):
680 58a6c894 Antony Chazapis
    """Return a maximum of blocksize data read from the socket in each iteration.
681 2715ade4 Sofia Papagiannaki

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

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

1053 b956618e Antony Chazapis
    Valid formats are 'text' and 'json', 'xml' if 'format_allowed' is True.
1054 b956618e Antony Chazapis
    """
1055 2715ade4 Sofia Papagiannaki
1056 b956618e Antony Chazapis
    if not format_allowed:
1057 b956618e Antony Chazapis
        return 'text'
1058 2715ade4 Sofia Papagiannaki
1059 b956618e Antony Chazapis
    format = request.GET.get('format')
1060 b956618e Antony Chazapis
    if format == 'json':
1061 b956618e Antony Chazapis
        return 'json'
1062 b956618e Antony Chazapis
    elif format == 'xml':
1063 b956618e Antony Chazapis
        return 'xml'
1064 2715ade4 Sofia Papagiannaki
1065 f9f15f92 Antony Chazapis
    for item in request.META.get('HTTP_ACCEPT', '').split(','):
1066 f9f15f92 Antony Chazapis
        accept, sep, rest = item.strip().partition(';')
1067 f9f15f92 Antony Chazapis
        if accept == 'application/json':
1068 f9f15f92 Antony Chazapis
            return 'json'
1069 f9f15f92 Antony Chazapis
        elif accept == 'application/xml' or accept == 'text/xml':
1070 f9f15f92 Antony Chazapis
            return 'xml'
1071 2715ade4 Sofia Papagiannaki
1072 b956618e Antony Chazapis
    return 'text'
1073 b956618e Antony Chazapis
1074 c846fad1 Sofia Papagiannaki
def get_pithos_usage(usage):
1075 c846fad1 Sofia Papagiannaki
    for u in usage:
1076 c846fad1 Sofia Papagiannaki
        if u.get('name') == 'pithos+.diskspace':
1077 c846fad1 Sofia Papagiannaki
            return u
1078 c846fad1 Sofia Papagiannaki
1079 c846fad1 Sofia Papagiannaki
def api_method(http_method=None, format_allowed=False, user_required=True,
1080 c846fad1 Sofia Papagiannaki
        request_usage=False):
1081 58a6c894 Antony Chazapis
    """Decorator function for views that implement an API method."""
1082 2715ade4 Sofia Papagiannaki
1083 b956618e Antony Chazapis
    def decorator(func):
1084 b956618e Antony Chazapis
        @wraps(func)
1085 b956618e Antony Chazapis
        def wrapper(request, *args, **kwargs):
1086 b956618e Antony Chazapis
            try:
1087 b956618e Antony Chazapis
                if http_method and request.method != http_method:
1088 b956618e Antony Chazapis
                    raise BadRequest('Method not allowed.')
1089 2715ade4 Sofia Papagiannaki
1090 dfdf4802 Sofia Papagiannaki
                if user_required:
1091 dfdf4802 Sofia Papagiannaki
                    token = None
1092 dfdf4802 Sofia Papagiannaki
                    if request.method in ('HEAD', 'GET') and COOKIE_NAME in request.COOKIES:
1093 2715ade4 Sofia Papagiannaki
                        cookie_value = unquote(
1094 2715ade4 Sofia Papagiannaki
                            request.COOKIES.get(COOKIE_NAME, ''))
1095 bf45cb4a Sofia Papagiannaki
                        account, sep, token = cookie_value.partition('|')
1096 2715ade4 Sofia Papagiannaki
                    get_user(request,
1097 c846fad1 Sofia Papagiannaki
                             AUTHENTICATION_URL,
1098 c846fad1 Sofia Papagiannaki
                             AUTHENTICATION_USERS,
1099 c846fad1 Sofia Papagiannaki
                             token,
1100 2150482f Sofia Papagiannaki
                             request_usage)
1101 dfdf4802 Sofia Papagiannaki
                    if  getattr(request, 'user', None) is None:
1102 dfdf4802 Sofia Papagiannaki
                        raise Unauthorized('Access denied')
1103 b3102a96 Sofia Papagiannaki
                    assert getattr(request, 'user_uniq', None) != None
1104 27932481 Sofia Papagiannaki
                    request.user_usage = get_pithos_usage(request.user.get('usage', []))
1105 27932481 Sofia Papagiannaki
                    request.token = request.GET.get('X-Auth-Token', request.META.get('HTTP_X_AUTH_TOKEN', token))
1106 27932481 Sofia Papagiannaki
1107 b956618e Antony Chazapis
                # The args variable may contain up to (account, container, object).
1108 b956618e Antony Chazapis
                if len(args) > 1 and len(args[1]) > 256:
1109 b956618e Antony Chazapis
                    raise BadRequest('Container name too large.')
1110 b956618e Antony Chazapis
                if len(args) > 2 and len(args[2]) > 1024:
1111 b956618e Antony Chazapis
                    raise BadRequest('Object name too large.')
1112 2715ade4 Sofia Papagiannaki
1113 9fefc052 Antony Chazapis
                # Format and check headers.
1114 9fefc052 Antony Chazapis
                update_request_headers(request)
1115 2715ade4 Sofia Papagiannaki
1116 b956618e Antony Chazapis
                # Fill in custom request variables.
1117 2715ade4 Sofia Papagiannaki
                request.serialization = request_serialization(
1118 2715ade4 Sofia Papagiannaki
                    request, format_allowed)
1119 228de81b Antony Chazapis
                request.backend = get_backend()
1120 2715ade4 Sofia Papagiannaki
1121 b956618e Antony Chazapis
                response = func(request, *args, **kwargs)
1122 b956618e Antony Chazapis
                update_response_headers(request, response)
1123 b956618e Antony Chazapis
                return response
1124 b956618e Antony Chazapis
            except Fault, fault:
1125 6e9c8fc1 Christos Stavrakakis
                if fault.code >= 500:
1126 6e9c8fc1 Christos Stavrakakis
                    logger.exception("API Fault")
1127 b956618e Antony Chazapis
                return render_fault(request, fault)
1128 b956618e Antony Chazapis
            except BaseException, e:
1129 b956618e Antony Chazapis
                logger.exception('Unexpected error: %s' % e)
1130 f5a1e653 Sofia Papagiannaki
                fault = InternalServerError('Unexpected error')
1131 b956618e Antony Chazapis
                return render_fault(request, fault)
1132 7b25e082 Antony Chazapis
            finally:
1133 297513ba Antony Chazapis
                if getattr(request, 'backend', None) is not None:
1134 d14fe290 Antony Chazapis
                    request.backend.close()
1135 b956618e Antony Chazapis
        return wrapper
1136 b956618e Antony Chazapis
    return decorator