Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ 981d3b0d

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

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

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

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

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

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

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

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