Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ 9f135224

History | View | Annotate | Download (42.1 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 65bbcd43 Christos Stavrakakis
from datetime import datetime
36 9fefc052 Antony Chazapis
from urllib import quote, unquote
37 b956618e Antony Chazapis
38 d2d09227 Sofia Papagiannaki
from django.http import HttpResponse, Http404, HttpResponseForbidden
39 6b6b6c1e Antony Chazapis
from django.template.loader import render_to_string
40 1993fea9 Antony Chazapis
from django.utils import simplejson as json
41 22dab079 Antony Chazapis
from django.utils.http import http_date, parse_etags
42 fb064032 Antony Chazapis
from django.utils.encoding import smart_unicode, smart_str
43 817890f2 Antony Chazapis
from django.core.files.uploadhandler import FileUploadHandler
44 817890f2 Antony Chazapis
from django.core.files.uploadedfile import UploadedFile
45 a3fcee5b Sofia Papagiannaki
from django.core.urlresolvers import reverse
46 b956618e Antony Chazapis
47 896754a6 Christos Stavrakakis
from snf_django.lib.api.parsedate import parse_http_date_safe, parse_http_date
48 65bbcd43 Christos Stavrakakis
from snf_django.lib import api
49 65bbcd43 Christos Stavrakakis
from snf_django.lib.api import faults, utils
50 5a96180b Antony Chazapis
51 a7dff008 Antony Chazapis
from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION,
52 761c2b3c root
                                 BACKEND_BLOCK_MODULE, BACKEND_BLOCK_PATH,
53 761c2b3c root
                                 BACKEND_BLOCK_UMASK,
54 f4fbb0fa Sofia Papagiannaki
                                 BACKEND_QUEUE_MODULE, BACKEND_QUEUE_HOSTS,
55 b17e5550 Giorgos Korfiatis
                                 BACKEND_QUEUE_EXCHANGE,
56 16f2673e Sofia Papagiannaki
                                 ASTAKOSCLIENT_POOLSIZE,
57 b17e5550 Giorgos Korfiatis
                                 SERVICE_TOKEN,
58 e3ff6830 Georgios D. Tsoukalas
                                 ASTAKOS_BASE_URL,
59 19ddd41b Sofia Papagiannaki
                                 BACKEND_ACCOUNT_QUOTA, BACKEND_CONTAINER_QUOTA,
60 19ddd41b Sofia Papagiannaki
                                 BACKEND_VERSIONING,
61 61c5b615 Sofia Papagiannaki
                                 BACKEND_FREE_VERSIONING, BACKEND_POOL_SIZE,
62 47462eda Filippos Giannakos
                                 RADOS_STORAGE, RADOS_POOL_BLOCKS,
63 56f3c759 Sofia Papagiannaki
                                 RADOS_POOL_MAPS, TRANSLATE_UUIDS,
64 4a105ce2 Sofia Papagiannaki
                                 PUBLIC_URL_SECURITY,
65 a3fcee5b Sofia Papagiannaki
                                 PUBLIC_URL_ALPHABET,
66 cc1f91ad Sofia Papagiannaki
                                 COOKIE_NAME, BASE_HOST)
67 02de6286 Sofia Papagiannaki
from pithos.api.resources import resources
68 32454501 Sofia Papagiannaki
from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists,
69 32454501 Sofia Papagiannaki
                                  VersionNotExists)
70 a3fcee5b Sofia Papagiannaki
71 a3fcee5b Sofia Papagiannaki
from synnefo.lib import join_urls
72 a3fcee5b Sofia Papagiannaki
73 e6fb591c Ilias Tsitsimpis
from astakosclient import AstakosClient
74 e6fb591c Ilias Tsitsimpis
from astakosclient.errors import NoUserName, NoUUID
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
logger = logging.getLogger(__name__)
83 b956618e Antony Chazapis
84 b956618e Antony Chazapis
85 c48acbfd Sofia Papagiannaki
def json_encode_decimal(obj):
86 c48acbfd Sofia Papagiannaki
    if isinstance(obj, decimal.Decimal):
87 c48acbfd Sofia Papagiannaki
        return str(obj)
88 c48acbfd Sofia Papagiannaki
    raise TypeError(repr(obj) + " is not JSON serializable")
89 c48acbfd Sofia Papagiannaki
90 2715ade4 Sofia Papagiannaki
91 804e8fe7 Antony Chazapis
def rename_meta_key(d, old, new):
92 804e8fe7 Antony Chazapis
    if old not in d:
93 804e8fe7 Antony Chazapis
        return
94 804e8fe7 Antony Chazapis
    d[new] = d[old]
95 804e8fe7 Antony Chazapis
    del(d[old])
96 804e8fe7 Antony Chazapis
97 2715ade4 Sofia Papagiannaki
98 02c0c3fa Antony Chazapis
def printable_header_dict(d):
99 b956618e Antony Chazapis
    """Format a meta dictionary for printing out json/xml.
100 2715ade4 Sofia Papagiannaki

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

523 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
524 22dab079 Antony Chazapis
    or a list of (offset, length) tuples - should be further checked.
525 b956618e Antony Chazapis
    """
526 2715ade4 Sofia Papagiannaki
527 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_RANGE', '').replace(' ', '')
528 22dab079 Antony Chazapis
    if not ranges.startswith('bytes='):
529 b956618e Antony Chazapis
        return None
530 2715ade4 Sofia Papagiannaki
531 22dab079 Antony Chazapis
    ret = []
532 22dab079 Antony Chazapis
    for r in (x.strip() for x in ranges[6:].split(',')):
533 22dab079 Antony Chazapis
        p = re.compile('^(?P<offset>\d*)-(?P<upto>\d*)$')
534 22dab079 Antony Chazapis
        m = p.match(r)
535 22dab079 Antony Chazapis
        if not m:
536 22dab079 Antony Chazapis
            return None
537 22dab079 Antony Chazapis
        offset = m.group('offset')
538 22dab079 Antony Chazapis
        upto = m.group('upto')
539 22dab079 Antony Chazapis
        if offset == '' and upto == '':
540 b956618e Antony Chazapis
            return None
541 2715ade4 Sofia Papagiannaki
542 22dab079 Antony Chazapis
        if offset != '':
543 22dab079 Antony Chazapis
            offset = int(offset)
544 22dab079 Antony Chazapis
            if upto != '':
545 b956618e Antony Chazapis
                upto = int(upto)
546 22dab079 Antony Chazapis
                if offset > upto:
547 22dab079 Antony Chazapis
                    return None
548 22dab079 Antony Chazapis
                ret.append((offset, upto - offset + 1))
549 22dab079 Antony Chazapis
            else:
550 22dab079 Antony Chazapis
                ret.append((offset, size - offset))
551 b956618e Antony Chazapis
        else:
552 22dab079 Antony Chazapis
            length = int(upto)
553 22dab079 Antony Chazapis
            ret.append((size - length, length))
554 2715ade4 Sofia Papagiannaki
555 22dab079 Antony Chazapis
    return ret
556 22dab079 Antony Chazapis
557 2715ade4 Sofia Papagiannaki
558 22dab079 Antony Chazapis
def get_content_range(request):
559 58a6c894 Antony Chazapis
    """Parse a Content-Range header from the request.
560 2715ade4 Sofia Papagiannaki

561 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
562 22dab079 Antony Chazapis
    or an (offset, length, total) tuple - check as length, total may be None.
563 22dab079 Antony Chazapis
    Returns (None, None, None) if the provided range is '*/*'.
564 22dab079 Antony Chazapis
    """
565 2715ade4 Sofia Papagiannaki
566 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_CONTENT_RANGE', '')
567 22dab079 Antony Chazapis
    if not ranges:
568 22dab079 Antony Chazapis
        return None
569 2715ade4 Sofia Papagiannaki
570 22dab079 Antony Chazapis
    p = re.compile('^bytes (?P<offset>\d+)-(?P<upto>\d*)/(?P<total>(\d+|\*))$')
571 22dab079 Antony Chazapis
    m = p.match(ranges)
572 22dab079 Antony Chazapis
    if not m:
573 22dab079 Antony Chazapis
        if ranges == 'bytes */*':
574 22dab079 Antony Chazapis
            return (None, None, None)
575 22dab079 Antony Chazapis
        return None
576 22dab079 Antony Chazapis
    offset = int(m.group('offset'))
577 22dab079 Antony Chazapis
    upto = m.group('upto')
578 22dab079 Antony Chazapis
    total = m.group('total')
579 22dab079 Antony Chazapis
    if upto != '':
580 22dab079 Antony Chazapis
        upto = int(upto)
581 b956618e Antony Chazapis
    else:
582 22dab079 Antony Chazapis
        upto = None
583 22dab079 Antony Chazapis
    if total != '*':
584 22dab079 Antony Chazapis
        total = int(total)
585 22dab079 Antony Chazapis
    else:
586 22dab079 Antony Chazapis
        total = None
587 70e526a0 Antony Chazapis
    if (upto is not None and offset > upto) or \
588 70e526a0 Antony Chazapis
        (total is not None and offset >= total) or \
589 2715ade4 Sofia Papagiannaki
            (total is not None and upto is not None and upto >= total):
590 22dab079 Antony Chazapis
        return None
591 2715ade4 Sofia Papagiannaki
592 70e526a0 Antony Chazapis
    if upto is None:
593 22dab079 Antony Chazapis
        length = None
594 22dab079 Antony Chazapis
    else:
595 22dab079 Antony Chazapis
        length = upto - offset + 1
596 22dab079 Antony Chazapis
    return (offset, length, total)
597 b956618e Antony Chazapis
598 2715ade4 Sofia Papagiannaki
599 3436eeb0 Antony Chazapis
def get_sharing(request):
600 3436eeb0 Antony Chazapis
    """Parse an X-Object-Sharing header from the request.
601 2715ade4 Sofia Papagiannaki

602 3436eeb0 Antony Chazapis
    Raises BadRequest on error.
603 3436eeb0 Antony Chazapis
    """
604 2715ade4 Sofia Papagiannaki
605 3436eeb0 Antony Chazapis
    permissions = request.META.get('HTTP_X_OBJECT_SHARING')
606 cca6c617 Antony Chazapis
    if permissions is None:
607 3436eeb0 Antony Chazapis
        return None
608 2715ade4 Sofia Papagiannaki
609 0a7f1671 Antony Chazapis
    # TODO: Document or remove '~' replacing.
610 0a7f1671 Antony Chazapis
    permissions = permissions.replace('~', '')
611 2715ade4 Sofia Papagiannaki
612 3436eeb0 Antony Chazapis
    ret = {}
613 cca6c617 Antony Chazapis
    permissions = permissions.replace(' ', '')
614 cca6c617 Antony Chazapis
    if permissions == '':
615 cca6c617 Antony Chazapis
        return ret
616 cca6c617 Antony Chazapis
    for perm in (x for x in permissions.split(';')):
617 cca6c617 Antony Chazapis
        if perm.startswith('read='):
618 2715ade4 Sofia Papagiannaki
            ret['read'] = list(set(
619 2715ade4 Sofia Papagiannaki
                [v.replace(' ', '').lower() for v in perm[5:].split(',')]))
620 02c0c3fa Antony Chazapis
            if '' in ret['read']:
621 02c0c3fa Antony Chazapis
                ret['read'].remove('')
622 e8886082 Antony Chazapis
            if '*' in ret['read']:
623 e8886082 Antony Chazapis
                ret['read'] = ['*']
624 3436eeb0 Antony Chazapis
            if len(ret['read']) == 0:
625 bd40abfa Christos Stavrakakis
                raise faults.BadRequest(
626 32454501 Sofia Papagiannaki
                    'Bad X-Object-Sharing header value: invalid length')
627 3436eeb0 Antony Chazapis
        elif perm.startswith('write='):
628 2715ade4 Sofia Papagiannaki
            ret['write'] = list(set(
629 2715ade4 Sofia Papagiannaki
                [v.replace(' ', '').lower() for v in perm[6:].split(',')]))
630 02c0c3fa Antony Chazapis
            if '' in ret['write']:
631 02c0c3fa Antony Chazapis
                ret['write'].remove('')
632 e8886082 Antony Chazapis
            if '*' in ret['write']:
633 e8886082 Antony Chazapis
                ret['write'] = ['*']
634 3436eeb0 Antony Chazapis
            if len(ret['write']) == 0:
635 bd40abfa Christos Stavrakakis
                raise faults.BadRequest(
636 32454501 Sofia Papagiannaki
                    'Bad X-Object-Sharing header value: invalid length')
637 3436eeb0 Antony Chazapis
        else:
638 bd40abfa Christos Stavrakakis
            raise faults.BadRequest(
639 32454501 Sofia Papagiannaki
                'Bad X-Object-Sharing header value: missing prefix')
640 32454501 Sofia Papagiannaki
641 890c2065 Sofia Papagiannaki
    # replace displayname with uuid
642 469d0997 Georgios D. Tsoukalas
    if TRANSLATE_UUIDS:
643 469d0997 Georgios D. Tsoukalas
        try:
644 469d0997 Georgios D. Tsoukalas
            ret['read'] = [replace_permissions_displayname(
645 ad0efdb3 Ilias Tsitsimpis
                getattr(request, 'token', None), x)
646 ad0efdb3 Ilias Tsitsimpis
                for x in ret.get('read', [])]
647 469d0997 Georgios D. Tsoukalas
            ret['write'] = [replace_permissions_displayname(
648 ad0efdb3 Ilias Tsitsimpis
                getattr(request, 'token', None), x)
649 ad0efdb3 Ilias Tsitsimpis
                for x in ret.get('write', [])]
650 469d0997 Georgios D. Tsoukalas
        except ItemNotExists, e:
651 bd40abfa Christos Stavrakakis
            raise faults.BadRequest(
652 469d0997 Georgios D. Tsoukalas
                'Bad X-Object-Sharing header value: unknown account: %s' % e)
653 2715ade4 Sofia Papagiannaki
654 21a8a6ff Antony Chazapis
    # Keep duplicates only in write list.
655 2715ade4 Sofia Papagiannaki
    dups = [x for x in ret.get(
656 2715ade4 Sofia Papagiannaki
        'read', []) if x in ret.get('write', []) and x != '*']
657 21a8a6ff Antony Chazapis
    if dups:
658 21a8a6ff Antony Chazapis
        for x in dups:
659 21a8a6ff Antony Chazapis
            ret['read'].remove(x)
660 21a8a6ff Antony Chazapis
        if len(ret['read']) == 0:
661 21a8a6ff Antony Chazapis
            del(ret['read'])
662 2715ade4 Sofia Papagiannaki
663 3436eeb0 Antony Chazapis
    return ret
664 3436eeb0 Antony Chazapis
665 2715ade4 Sofia Papagiannaki
666 3ab38c43 Antony Chazapis
def get_public(request):
667 3ab38c43 Antony Chazapis
    """Parse an X-Object-Public header from the request.
668 2715ade4 Sofia Papagiannaki

669 3ab38c43 Antony Chazapis
    Raises BadRequest on error.
670 3ab38c43 Antony Chazapis
    """
671 2715ade4 Sofia Papagiannaki
672 3ab38c43 Antony Chazapis
    public = request.META.get('HTTP_X_OBJECT_PUBLIC')
673 3ab38c43 Antony Chazapis
    if public is None:
674 3ab38c43 Antony Chazapis
        return None
675 2715ade4 Sofia Papagiannaki
676 3ab38c43 Antony Chazapis
    public = public.replace(' ', '').lower()
677 3ab38c43 Antony Chazapis
    if public == 'true':
678 3ab38c43 Antony Chazapis
        return True
679 3ab38c43 Antony Chazapis
    elif public == 'false' or public == '':
680 3ab38c43 Antony Chazapis
        return False
681 bd40abfa Christos Stavrakakis
    raise faults.BadRequest('Bad X-Object-Public header value')
682 3ab38c43 Antony Chazapis
683 2715ade4 Sofia Papagiannaki
684 b956618e Antony Chazapis
def raw_input_socket(request):
685 58a6c894 Antony Chazapis
    """Return the socket for reading the rest of the request."""
686 2715ade4 Sofia Papagiannaki
687 b956618e Antony Chazapis
    server_software = request.META.get('SERVER_SOFTWARE')
688 fc1b2a75 Antony Chazapis
    if server_software and server_software.startswith('mod_python'):
689 b956618e Antony Chazapis
        return request._req
690 fc1b2a75 Antony Chazapis
    if 'wsgi.input' in request.environ:
691 fc1b2a75 Antony Chazapis
        return request.environ['wsgi.input']
692 08de868d Antony Chazapis
    raise NotImplemented('Unknown server software')
693 b956618e Antony Chazapis
694 2715ade4 Sofia Papagiannaki
MAX_UPLOAD_SIZE = 5 * (1024 * 1024 * 1024)  # 5GB
695 2715ade4 Sofia Papagiannaki
696 b956618e Antony Chazapis
697 c032f34d Antony Chazapis
def socket_read_iterator(request, length=0, blocksize=4096):
698 ad0efdb3 Ilias Tsitsimpis
    """Return a maximum of blocksize data read from the socket in each iteration
699 2715ade4 Sofia Papagiannaki

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

804 ad0efdb3 Ilias Tsitsimpis
    Read from the object using the offset and length provided
805 ad0efdb3 Ilias Tsitsimpis
    in each entry of the range list.
806 22dab079 Antony Chazapis
    """
807 2715ade4 Sofia Papagiannaki
808 39593b2b Giorgos Verigakis
    def __init__(self, backend, ranges, sizes, hashmaps, boundary):
809 39593b2b Giorgos Verigakis
        self.backend = backend
810 22dab079 Antony Chazapis
        self.ranges = ranges
811 8cb45c13 Antony Chazapis
        self.sizes = sizes
812 8cb45c13 Antony Chazapis
        self.hashmaps = hashmaps
813 22dab079 Antony Chazapis
        self.boundary = boundary
814 8cb45c13 Antony Chazapis
        self.size = sum(self.sizes)
815 2715ade4 Sofia Papagiannaki
816 8cb45c13 Antony Chazapis
        self.file_index = 0
817 8cb45c13 Antony Chazapis
        self.block_index = 0
818 8cb45c13 Antony Chazapis
        self.block_hash = -1
819 22dab079 Antony Chazapis
        self.block = ''
820 2715ade4 Sofia Papagiannaki
821 22dab079 Antony Chazapis
        self.range_index = -1
822 22dab079 Antony Chazapis
        self.offset, self.length = self.ranges[0]
823 2715ade4 Sofia Papagiannaki
824 22dab079 Antony Chazapis
    def __iter__(self):
825 22dab079 Antony Chazapis
        return self
826 2715ade4 Sofia Papagiannaki
827 22dab079 Antony Chazapis
    def part_iterator(self):
828 22dab079 Antony Chazapis
        if self.length > 0:
829 8cb45c13 Antony Chazapis
            # Get the file for the current offset.
830 8cb45c13 Antony Chazapis
            file_size = self.sizes[self.file_index]
831 8cb45c13 Antony Chazapis
            while self.offset >= file_size:
832 8cb45c13 Antony Chazapis
                self.offset -= file_size
833 8cb45c13 Antony Chazapis
                self.file_index += 1
834 8cb45c13 Antony Chazapis
                file_size = self.sizes[self.file_index]
835 2715ade4 Sofia Papagiannaki
836 8cb45c13 Antony Chazapis
            # Get the block for the current position.
837 39593b2b Giorgos Verigakis
            self.block_index = int(self.offset / self.backend.block_size)
838 ad0efdb3 Ilias Tsitsimpis
            if self.block_hash != \
839 ad0efdb3 Ilias Tsitsimpis
                    self.hashmaps[self.file_index][self.block_index]:
840 2715ade4 Sofia Papagiannaki
                self.block_hash = self.hashmaps[
841 2715ade4 Sofia Papagiannaki
                    self.file_index][self.block_index]
842 22dab079 Antony Chazapis
                try:
843 39593b2b Giorgos Verigakis
                    self.block = self.backend.get_block(self.block_hash)
844 7efc9f86 Sofia Papagiannaki
                except ItemNotExists:
845 bd40abfa Christos Stavrakakis
                    raise faults.ItemNotFound('Block does not exist')
846 2715ade4 Sofia Papagiannaki
847 22dab079 Antony Chazapis
            # Get the data from the block.
848 39593b2b Giorgos Verigakis
            bo = self.offset % self.backend.block_size
849 c9865fe1 Antony Chazapis
            bs = self.backend.block_size
850 45cf0bc8 Antony Chazapis
            if (self.block_index == len(self.hashmaps[self.file_index]) - 1 and
851 2715ade4 Sofia Papagiannaki
                    self.sizes[self.file_index] % self.backend.block_size):
852 c9865fe1 Antony Chazapis
                bs = self.sizes[self.file_index] % self.backend.block_size
853 c9865fe1 Antony Chazapis
            bl = min(self.length, bs - bo)
854 22dab079 Antony Chazapis
            data = self.block[bo:bo + bl]
855 22dab079 Antony Chazapis
            self.offset += bl
856 22dab079 Antony Chazapis
            self.length -= bl
857 22dab079 Antony Chazapis
            return data
858 22dab079 Antony Chazapis
        else:
859 22dab079 Antony Chazapis
            raise StopIteration
860 2715ade4 Sofia Papagiannaki
861 22dab079 Antony Chazapis
    def next(self):
862 22dab079 Antony Chazapis
        if len(self.ranges) == 1:
863 22dab079 Antony Chazapis
            return self.part_iterator()
864 22dab079 Antony Chazapis
        if self.range_index == len(self.ranges):
865 22dab079 Antony Chazapis
            raise StopIteration
866 22dab079 Antony Chazapis
        try:
867 22dab079 Antony Chazapis
            if self.range_index == -1:
868 22dab079 Antony Chazapis
                raise StopIteration
869 22dab079 Antony Chazapis
            return self.part_iterator()
870 22dab079 Antony Chazapis
        except StopIteration:
871 22dab079 Antony Chazapis
            self.range_index += 1
872 22dab079 Antony Chazapis
            out = []
873 22dab079 Antony Chazapis
            if self.range_index < len(self.ranges):
874 22dab079 Antony Chazapis
                # Part header.
875 22dab079 Antony Chazapis
                self.offset, self.length = self.ranges[self.range_index]
876 8cb45c13 Antony Chazapis
                self.file_index = 0
877 22dab079 Antony Chazapis
                if self.range_index > 0:
878 22dab079 Antony Chazapis
                    out.append('')
879 22dab079 Antony Chazapis
                out.append('--' + self.boundary)
880 2715ade4 Sofia Papagiannaki
                out.append('Content-Range: bytes %d-%d/%d' % (
881 2715ade4 Sofia Papagiannaki
                    self.offset, self.offset + self.length - 1, self.size))
882 22dab079 Antony Chazapis
                out.append('Content-Transfer-Encoding: binary')
883 22dab079 Antony Chazapis
                out.append('')
884 22dab079 Antony Chazapis
                out.append('')
885 22dab079 Antony Chazapis
                return '\r\n'.join(out)
886 22dab079 Antony Chazapis
            else:
887 22dab079 Antony Chazapis
                # Footer.
888 22dab079 Antony Chazapis
                out.append('')
889 22dab079 Antony Chazapis
                out.append('--' + self.boundary + '--')
890 22dab079 Antony Chazapis
                out.append('')
891 22dab079 Antony Chazapis
                return '\r\n'.join(out)
892 22dab079 Antony Chazapis
893 2715ade4 Sofia Papagiannaki
894 8cb45c13 Antony Chazapis
def object_data_response(request, sizes, hashmaps, meta, public=False):
895 7bef5750 Antony Chazapis
    """Get the HttpResponse object for replying with the object's data."""
896 2715ade4 Sofia Papagiannaki
897 7bef5750 Antony Chazapis
    # Range handling.
898 8cb45c13 Antony Chazapis
    size = sum(sizes)
899 7bef5750 Antony Chazapis
    ranges = get_range(request, size)
900 7bef5750 Antony Chazapis
    if ranges is None:
901 7bef5750 Antony Chazapis
        ranges = [(0, size)]
902 7bef5750 Antony Chazapis
        ret = 200
903 7bef5750 Antony Chazapis
    else:
904 7bef5750 Antony Chazapis
        check = [True for offset, length in ranges if
905 2715ade4 Sofia Papagiannaki
                 length <= 0 or length > size or
906 2715ade4 Sofia Papagiannaki
                 offset < 0 or offset >= size or
907 2715ade4 Sofia Papagiannaki
                 offset + length > size]
908 7bef5750 Antony Chazapis
        if len(check) > 0:
909 ad0efdb3 Ilias Tsitsimpis
            raise faults.RangeNotSatisfiable(
910 ad0efdb3 Ilias Tsitsimpis
                'Requested range exceeds object limits')
911 7bef5750 Antony Chazapis
        ret = 206
912 15d465b8 Antony Chazapis
        if_range = request.META.get('HTTP_IF_RANGE')
913 15d465b8 Antony Chazapis
        if if_range:
914 6d1e6dce Sofia Papagiannaki
            try:
915 15d465b8 Antony Chazapis
                # Modification time has passed instead.
916 6d1e6dce Sofia Papagiannaki
                last_modified = parse_http_date(if_range)
917 6d1e6dce Sofia Papagiannaki
                if last_modified != meta['modified']:
918 6d1e6dce Sofia Papagiannaki
                    ranges = [(0, size)]
919 6d1e6dce Sofia Papagiannaki
                    ret = 200
920 6d1e6dce Sofia Papagiannaki
            except ValueError:
921 33b4e4a6 Antony Chazapis
                if if_range != meta['checksum']:
922 6d1e6dce Sofia Papagiannaki
                    ranges = [(0, size)]
923 6d1e6dce Sofia Papagiannaki
                    ret = 200
924 2715ade4 Sofia Papagiannaki
925 7bef5750 Antony Chazapis
    if ret == 206 and len(ranges) > 1:
926 7bef5750 Antony Chazapis
        boundary = uuid.uuid4().hex
927 7bef5750 Antony Chazapis
    else:
928 7bef5750 Antony Chazapis
        boundary = ''
929 39593b2b Giorgos Verigakis
    wrapper = ObjectWrapper(request.backend, ranges, sizes, hashmaps, boundary)
930 7bef5750 Antony Chazapis
    response = HttpResponse(wrapper, status=ret)
931 469d0997 Georgios D. Tsoukalas
    put_object_headers(
932 ad0efdb3 Ilias Tsitsimpis
        response, meta, restricted=public,
933 ad0efdb3 Ilias Tsitsimpis
        token=getattr(request, 'token', None))
934 7bef5750 Antony Chazapis
    if ret == 206:
935 7bef5750 Antony Chazapis
        if len(ranges) == 1:
936 7bef5750 Antony Chazapis
            offset, length = ranges[0]
937 2715ade4 Sofia Papagiannaki
            response[
938 2715ade4 Sofia Papagiannaki
                'Content-Length'] = length  # Update with the correct length.
939 2715ade4 Sofia Papagiannaki
            response['Content-Range'] = 'bytes %d-%d/%d' % (
940 2715ade4 Sofia Papagiannaki
                offset, offset + length - 1, size)
941 7bef5750 Antony Chazapis
        else:
942 7bef5750 Antony Chazapis
            del(response['Content-Length'])
943 2715ade4 Sofia Papagiannaki
            response['Content-Type'] = 'multipart/byteranges; boundary=%s' % (
944 2715ade4 Sofia Papagiannaki
                boundary,)
945 7bef5750 Antony Chazapis
    return response
946 7bef5750 Antony Chazapis
947 2715ade4 Sofia Papagiannaki
948 39593b2b Giorgos Verigakis
def put_object_block(request, hashmap, data, offset):
949 cb146cf9 Antony Chazapis
    """Put one block of data at the given offset."""
950 2715ade4 Sofia Papagiannaki
951 39593b2b Giorgos Verigakis
    bi = int(offset / request.backend.block_size)
952 39593b2b Giorgos Verigakis
    bo = offset % request.backend.block_size
953 39593b2b Giorgos Verigakis
    bl = min(len(data), request.backend.block_size - bo)
954 cb146cf9 Antony Chazapis
    if bi < len(hashmap):
955 39593b2b Giorgos Verigakis
        hashmap[bi] = request.backend.update_block(hashmap[bi], data[:bl], bo)
956 cb146cf9 Antony Chazapis
    else:
957 39593b2b Giorgos Verigakis
        hashmap.append(request.backend.put_block(('\x00' * bo) + data[:bl]))
958 2715ade4 Sofia Papagiannaki
    return bl  # Return ammount of data written.
959 2715ade4 Sofia Papagiannaki
960 cb146cf9 Antony Chazapis
961 b3155065 Antony Chazapis
def hashmap_md5(backend, hashmap, size):
962 cddcf432 chazapis
    """Produce the MD5 sum from the data in the hashmap."""
963 2715ade4 Sofia Papagiannaki
964 ad0efdb3 Ilias Tsitsimpis
    # TODO: Search backend for the MD5 of another object
965 ad0efdb3 Ilias Tsitsimpis
    #       with the same hashmap and size...
966 cddcf432 chazapis
    md5 = hashlib.md5()
967 b3155065 Antony Chazapis
    bs = backend.block_size
968 cddcf432 chazapis
    for bi, hash in enumerate(hashmap):
969 2715ade4 Sofia Papagiannaki
        data = backend.get_block(hash)  # Blocks come in padded.
970 cddcf432 chazapis
        if bi == len(hashmap) - 1:
971 c9865fe1 Antony Chazapis
            data = data[:size % bs]
972 c9865fe1 Antony Chazapis
        md5.update(data)
973 cddcf432 chazapis
    return md5.hexdigest().lower()
974 49133392 Antony Chazapis
975 2715ade4 Sofia Papagiannaki
976 af7bb62f Antony Chazapis
def simple_list_response(request, l):
977 6b6b6c1e Antony Chazapis
    if request.serialization == 'text':
978 6b6b6c1e Antony Chazapis
        return '\n'.join(l) + '\n'
979 6b6b6c1e Antony Chazapis
    if request.serialization == 'xml':
980 af7bb62f Antony Chazapis
        return render_to_string('items.xml', {'items': l})
981 6b6b6c1e Antony Chazapis
    if request.serialization == 'json':
982 6b6b6c1e Antony Chazapis
        return json.dumps(l)
983 6b6b6c1e Antony Chazapis
984 35d42381 Vangelis Koukis
985 edb7875c Christos Stavrakakis
from pithos.backends.util import PithosBackendPool
986 61c5b615 Sofia Papagiannaki
987 47462eda Filippos Giannakos
if RADOS_STORAGE:
988 ad0efdb3 Ilias Tsitsimpis
    BLOCK_PARAMS = {'mappool': RADOS_POOL_MAPS,
989 ad0efdb3 Ilias Tsitsimpis
                    'blockpool': RADOS_POOL_BLOCKS, }
990 47462eda Filippos Giannakos
else:
991 ad0efdb3 Ilias Tsitsimpis
    BLOCK_PARAMS = {'mappool': None,
992 ad0efdb3 Ilias Tsitsimpis
                    'blockpool': None, }
993 35d42381 Vangelis Koukis
994 761c2b3c root
995 b336e6fa Georgios D. Tsoukalas
_pithos_backend_pool = PithosBackendPool(
996 61c5b615 Sofia Papagiannaki
        size=BACKEND_POOL_SIZE,
997 b336e6fa Georgios D. Tsoukalas
        db_module=BACKEND_DB_MODULE,
998 b336e6fa Georgios D. Tsoukalas
        db_connection=BACKEND_DB_CONNECTION,
999 b336e6fa Georgios D. Tsoukalas
        block_module=BACKEND_BLOCK_MODULE,
1000 b336e6fa Georgios D. Tsoukalas
        block_path=BACKEND_BLOCK_PATH,
1001 b336e6fa Georgios D. Tsoukalas
        block_umask=BACKEND_BLOCK_UMASK,
1002 b336e6fa Georgios D. Tsoukalas
        queue_module=BACKEND_QUEUE_MODULE,
1003 b336e6fa Georgios D. Tsoukalas
        queue_hosts=BACKEND_QUEUE_HOSTS,
1004 b336e6fa Georgios D. Tsoukalas
        queue_exchange=BACKEND_QUEUE_EXCHANGE,
1005 e3ff6830 Georgios D. Tsoukalas
        astakos_url=ASTAKOS_BASE_URL,
1006 16f2673e Sofia Papagiannaki
        service_token=SERVICE_TOKEN,
1007 16f2673e Sofia Papagiannaki
        astakosclient_poolsize=ASTAKOSCLIENT_POOLSIZE,
1008 b336e6fa Georgios D. Tsoukalas
        free_versioning=BACKEND_FREE_VERSIONING,
1009 56f3c759 Sofia Papagiannaki
        block_params=BLOCK_PARAMS,
1010 4a105ce2 Sofia Papagiannaki
        public_url_security=PUBLIC_URL_SECURITY,
1011 19ddd41b Sofia Papagiannaki
        public_url_alphabet=PUBLIC_URL_ALPHABET,
1012 19ddd41b Sofia Papagiannaki
        account_quota_policy=BACKEND_ACCOUNT_QUOTA,
1013 19ddd41b Sofia Papagiannaki
        container_quota_policy=BACKEND_CONTAINER_QUOTA,
1014 19ddd41b Sofia Papagiannaki
        container_versioning_policy=BACKEND_VERSIONING)
1015 35d42381 Vangelis Koukis
1016 65bbcd43 Christos Stavrakakis
1017 35d42381 Vangelis Koukis
def get_backend():
1018 edb7875c Christos Stavrakakis
    backend = _pithos_backend_pool.pool_get()
1019 f77b1da9 Stratos Psomadakis
    backend.messages = []
1020 edb7875c Christos Stavrakakis
    return backend
1021 35d42381 Vangelis Koukis
1022 35d42381 Vangelis Koukis
1023 9fefc052 Antony Chazapis
def update_request_headers(request):
1024 9fefc052 Antony Chazapis
    # Handle URL-encoded keys and values.
1025 2715ade4 Sofia Papagiannaki
    meta = dict([(
1026 2715ade4 Sofia Papagiannaki
        k, v) for k, v in request.META.iteritems() if k.startswith('HTTP_')])
1027 88283e9e Antony Chazapis
    for k, v in meta.iteritems():
1028 88283e9e Antony Chazapis
        try:
1029 88283e9e Antony Chazapis
            k.decode('ascii')
1030 88283e9e Antony Chazapis
            v.decode('ascii')
1031 88283e9e Antony Chazapis
        except UnicodeDecodeError:
1032 bd40abfa Christos Stavrakakis
            raise faults.BadRequest('Bad character in headers.')
1033 88283e9e Antony Chazapis
        if '%' in k or '%' in v:
1034 88283e9e Antony Chazapis
            del(request.META[k])
1035 2715ade4 Sofia Papagiannaki
            request.META[unquote(k)] = smart_unicode(unquote(
1036 2715ade4 Sofia Papagiannaki
                v), strings_only=True)
1037 2715ade4 Sofia Papagiannaki
1038 9fefc052 Antony Chazapis
1039 b956618e Antony Chazapis
def update_response_headers(request, response):
1040 9fefc052 Antony Chazapis
    if (not response.has_header('Content-Length') and
1041 9fefc052 Antony Chazapis
        not (response.has_header('Content-Type') and
1042 9fefc052 Antony Chazapis
             response['Content-Type'].startswith('multipart/byteranges'))):
1043 b980169d Antony Chazapis
        response['Content-Length'] = len(response.content)
1044 2715ade4 Sofia Papagiannaki
1045 9fefc052 Antony Chazapis
    # URL-encode unicode in headers.
1046 9fefc052 Antony Chazapis
    meta = response.items()
1047 9fefc052 Antony Chazapis
    for k, v in meta:
1048 88283e9e Antony Chazapis
        if (k.startswith('X-Account-') or k.startswith('X-Container-') or
1049 2715ade4 Sofia Papagiannaki
                k.startswith('X-Object-') or k.startswith('Content-')):
1050 9fefc052 Antony Chazapis
            del(response[k])
1051 88283e9e Antony Chazapis
            response[quote(k)] = quote(v, safe='/=,:@; ')
1052 b956618e Antony Chazapis
1053 2715ade4 Sofia Papagiannaki
1054 65bbcd43 Christos Stavrakakis
def get_pithos_usage(token):
1055 65bbcd43 Christos Stavrakakis
    """Get Pithos Usage from astakos."""
1056 e3ff6830 Georgios D. Tsoukalas
    astakos = AstakosClient(ASTAKOS_BASE_URL, retry=2, use_pool=True,
1057 e3ff6830 Georgios D. Tsoukalas
                            logger=logger)
1058 02de6286 Sofia Papagiannaki
    quotas = astakos.get_quotas(token)['system']
1059 02de6286 Sofia Papagiannaki
    pithos_resources = [r['name'] for r in resources]
1060 02de6286 Sofia Papagiannaki
    map(quotas.pop, filter(lambda k: k not in pithos_resources, quotas.keys()))
1061 02de6286 Sofia Papagiannaki
    return quotas.popitem()[-1] # assume only one resource
1062 c846fad1 Sofia Papagiannaki
1063 2715ade4 Sofia Papagiannaki
1064 b90584d0 Sofia Papagiannaki
def api_method(http_method=None, token_required=True, user_required=True,
1065 b90584d0 Sofia Papagiannaki
               logger=None, format_allowed=False, serializations=None,
1066 b90584d0 Sofia Papagiannaki
               strict_serlization=False, lock_container_path=False):
1067 d823a562 Kostas Papadimitriou
    serializations = serializations or ['json', 'xml']
1068 b956618e Antony Chazapis
    def decorator(func):
1069 d630c78b Sofia Papagiannaki
        @api.api_method(http_method=http_method, token_required=token_required,
1070 d630c78b Sofia Papagiannaki
                        user_required=user_required,
1071 47ef53d5 Christos Stavrakakis
                        logger=logger, format_allowed=format_allowed,
1072 6b560707 Sofia Papagiannaki
                        astakos_url=ASTAKOS_BASE_URL,
1073 b5aeeb2f Kostas Papadimitriou
                        serializations=serializations,
1074 b5aeeb2f Kostas Papadimitriou
                        strict_serlization=strict_serlization)
1075 b956618e Antony Chazapis
        @wraps(func)
1076 b956618e Antony Chazapis
        def wrapper(request, *args, **kwargs):
1077 65bbcd43 Christos Stavrakakis
            # The args variable may contain up to (account, container, object).
1078 65bbcd43 Christos Stavrakakis
            if len(args) > 1 and len(args[1]) > 256:
1079 65bbcd43 Christos Stavrakakis
                raise faults.BadRequest("Container name too large")
1080 65bbcd43 Christos Stavrakakis
            if len(args) > 2 and len(args[2]) > 1024:
1081 65bbcd43 Christos Stavrakakis
                raise faults.BadRequest('Object name too large.')
1082 2715ade4 Sofia Papagiannaki
1083 65bbcd43 Christos Stavrakakis
            try:
1084 65bbcd43 Christos Stavrakakis
                # Add a PithosBackend as attribute of the request object
1085 228de81b Antony Chazapis
                request.backend = get_backend()
1086 b90584d0 Sofia Papagiannaki
                request.backend.lock_container_path = lock_container_path
1087 9f135224 Sofia Papagiannaki
                request.backend.wrapper.execute()
1088 9f135224 Sofia Papagiannaki
                request.backend.serials = []
1089 9f135224 Sofia Papagiannaki
                request.backend.messages = []
1090 9f135224 Sofia Papagiannaki
1091 65bbcd43 Christos Stavrakakis
                # Many API method expect thet X-Auth-Token in request,token
1092 65bbcd43 Christos Stavrakakis
                request.token = request.x_auth_token
1093 65bbcd43 Christos Stavrakakis
                update_request_headers(request)
1094 b956618e Antony Chazapis
                response = func(request, *args, **kwargs)
1095 b956618e Antony Chazapis
                update_response_headers(request, response)
1096 9f135224 Sofia Papagiannaki
1097 9f135224 Sofia Papagiannaki
                # send messages produced
1098 9f135224 Sofia Papagiannaki
                for m in request.backend.messages:
1099 9f135224 Sofia Papagiannaki
                    request.backend.queue.send(*m)
1100 9f135224 Sofia Papagiannaki
1101 9f135224 Sofia Papagiannaki
                # register serials
1102 9f135224 Sofia Papagiannaki
                if request.backend.serials:
1103 9f135224 Sofia Papagiannaki
                    request.backend.commission_serials.insert_many(
1104 9f135224 Sofia Papagiannaki
                        request.backend.serials)
1105 9f135224 Sofia Papagiannaki
1106 9f135224 Sofia Papagiannaki
                    # commit to ensure that the serials are registered
1107 9f135224 Sofia Papagiannaki
                    # even if resolve commission fails
1108 9f135224 Sofia Papagiannaki
                    request.backend.wrapper.commit()
1109 9f135224 Sofia Papagiannaki
1110 9f135224 Sofia Papagiannaki
                    # start new transaction
1111 9f135224 Sofia Papagiannaki
                    request.backend.wrapper.execute()
1112 9f135224 Sofia Papagiannaki
1113 9f135224 Sofia Papagiannaki
                    r = request.backend.astakosclient.resolve_commissions(
1114 9f135224 Sofia Papagiannaki
                                token=request.backend.service_token,
1115 9f135224 Sofia Papagiannaki
                                accept_serials=request.backend.serials,
1116 9f135224 Sofia Papagiannaki
                                reject_serials=[])
1117 9f135224 Sofia Papagiannaki
                    request.backend.commission_serials.delete_many(
1118 9f135224 Sofia Papagiannaki
                        r['accepted'])
1119 9f135224 Sofia Papagiannaki
1120 9f135224 Sofia Papagiannaki
                request.backend.wrapper.commit()
1121 b956618e Antony Chazapis
                return response
1122 9f135224 Sofia Papagiannaki
            except:
1123 9f135224 Sofia Papagiannaki
                if request.backend.serials:
1124 9f135224 Sofia Papagiannaki
                    request.backend.astakosclient.resolve_commissions(
1125 9f135224 Sofia Papagiannaki
                        token=request.backend.service_token,
1126 9f135224 Sofia Papagiannaki
                        accept_serials=[],
1127 9f135224 Sofia Papagiannaki
                        reject_serials=request.backend.serials)
1128 9f135224 Sofia Papagiannaki
                request.backend.wrapper.rollback()
1129 9f135224 Sofia Papagiannaki
                raise
1130 7b25e082 Antony Chazapis
            finally:
1131 65bbcd43 Christos Stavrakakis
                # Always close PithosBackend connection
1132 65bbcd43 Christos Stavrakakis
                if getattr(request, "backend", None) is not None:
1133 d14fe290 Antony Chazapis
                    request.backend.close()
1134 b956618e Antony Chazapis
        return wrapper
1135 b956618e Antony Chazapis
    return decorator
1136 63de12cf Sofia Papagiannaki
1137 63de12cf Sofia Papagiannaki
1138 63de12cf Sofia Papagiannaki
def get_token_from_cookie(request):
1139 63de12cf Sofia Papagiannaki
    assert(request.method == 'GET'),\
1140 63de12cf Sofia Papagiannaki
        "Cookie based authentication is only allowed to GET requests"
1141 63de12cf Sofia Papagiannaki
    token = None
1142 63de12cf Sofia Papagiannaki
    if COOKIE_NAME in request.COOKIES:
1143 63de12cf Sofia Papagiannaki
        cookie_value = unquote(request.COOKIES.get(COOKIE_NAME, ''))
1144 63de12cf Sofia Papagiannaki
        account, sep, token = cookie_value.partition('|')
1145 63de12cf Sofia Papagiannaki
    return token
1146 63de12cf Sofia Papagiannaki
1147 63de12cf Sofia Papagiannaki
1148 63de12cf Sofia Papagiannaki
def view_method():
1149 63de12cf Sofia Papagiannaki
    """Decorator function for views."""
1150 63de12cf Sofia Papagiannaki
1151 63de12cf Sofia Papagiannaki
    def decorator(func):
1152 63de12cf Sofia Papagiannaki
        @wraps(func)
1153 63de12cf Sofia Papagiannaki
        def wrapper(request, *args, **kwargs):
1154 63de12cf Sofia Papagiannaki
            request.META['HTTP_X_AUTH_TOKEN'] = get_token_from_cookie(request)
1155 63de12cf Sofia Papagiannaki
            # Get the response object
1156 63de12cf Sofia Papagiannaki
            response = func(request, *args, **kwargs)
1157 63de12cf Sofia Papagiannaki
            if response.status_code == 200:
1158 63de12cf Sofia Papagiannaki
                return response
1159 d2d09227 Sofia Papagiannaki
            elif response.status_code == 404:
1160 d2d09227 Sofia Papagiannaki
                raise Http404()
1161 d2d09227 Sofia Papagiannaki
            elif response.status_code in [401, 403]:
1162 d2d09227 Sofia Papagiannaki
                return HttpResponseForbidden()
1163 63de12cf Sofia Papagiannaki
            else:
1164 d2d09227 Sofia Papagiannaki
                raise Exception(response)
1165 63de12cf Sofia Papagiannaki
        return wrapper
1166 63de12cf Sofia Papagiannaki
    return decorator