Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ 5ae33f6c

History | View | Annotate | Download (43.7 kB)

1 8f2eb016 Sofia Papagiannaki
# Copyright 2011-2013 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 8f2eb016 Sofia Papagiannaki
from urllib import quote, unquote, urlencode
37 b956618e Antony Chazapis
38 8f2eb016 Sofia Papagiannaki
from django.http import HttpResponse, Http404, HttpResponseRedirect
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 8f2eb016 Sofia Papagiannaki
from django.core.exceptions import PermissionDenied
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 f759041f Ilias Tsitsimpis
                                 ASTAKOS_AUTH_URL,
60 29148653 Sofia Papagiannaki
                                 BACKEND_ACCOUNT_QUOTA,
61 29148653 Sofia Papagiannaki
                                 BACKEND_CONTAINER_QUOTA,
62 29148653 Sofia Papagiannaki
                                 BACKEND_VERSIONING, BACKEND_FREE_VERSIONING,
63 74cbb94a Sofia Papagiannaki
                                 BACKEND_POOL_ENABLED, BACKEND_POOL_SIZE,
64 369a7b41 Sofia Papagiannaki
                                 BACKEND_BLOCK_SIZE, BACKEND_HASH_ALGORITHM,
65 47462eda Filippos Giannakos
                                 RADOS_STORAGE, RADOS_POOL_BLOCKS,
66 56f3c759 Sofia Papagiannaki
                                 RADOS_POOL_MAPS, TRANSLATE_UUIDS,
67 133e3fcf Sofia Papagiannaki
                                 PUBLIC_URL_SECURITY, PUBLIC_URL_ALPHABET,
68 8f2eb016 Sofia Papagiannaki
                                 BASE_HOST, UPDATE_MD5, VIEW_PREFIX,
69 8f2eb016 Sofia Papagiannaki
                                 OA2_CLIENT_CREDENTIALS)
70 d4e4e501 Christos Stavrakakis
71 02de6286 Sofia Papagiannaki
from pithos.api.resources import resources
72 74cbb94a Sofia Papagiannaki
from pithos.backends import connect_backend
73 32454501 Sofia Papagiannaki
from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists,
74 32454501 Sofia Papagiannaki
                                  VersionNotExists)
75 a3fcee5b Sofia Papagiannaki
76 a3fcee5b Sofia Papagiannaki
from synnefo.lib import join_urls
77 a3fcee5b Sofia Papagiannaki
78 e6fb591c Ilias Tsitsimpis
from astakosclient import AstakosClient
79 8f2eb016 Sofia Papagiannaki
from astakosclient.errors import NoUserName, NoUUID, AstakosClientException
80 b956618e Antony Chazapis
81 b956618e Antony Chazapis
import logging
82 22dab079 Antony Chazapis
import re
83 cbfb6636 Sofia Papagiannaki
import hashlib
84 7bef5750 Antony Chazapis
import uuid
85 c48acbfd Sofia Papagiannaki
import decimal
86 8c793655 Antony Chazapis
87 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
88 b956618e Antony Chazapis
89 b956618e Antony Chazapis
90 c48acbfd Sofia Papagiannaki
def json_encode_decimal(obj):
91 c48acbfd Sofia Papagiannaki
    if isinstance(obj, decimal.Decimal):
92 c48acbfd Sofia Papagiannaki
        return str(obj)
93 c48acbfd Sofia Papagiannaki
    raise TypeError(repr(obj) + " is not JSON serializable")
94 c48acbfd Sofia Papagiannaki
95 2715ade4 Sofia Papagiannaki
96 804e8fe7 Antony Chazapis
def rename_meta_key(d, old, new):
97 804e8fe7 Antony Chazapis
    if old not in d:
98 804e8fe7 Antony Chazapis
        return
99 804e8fe7 Antony Chazapis
    d[new] = d[old]
100 804e8fe7 Antony Chazapis
    del(d[old])
101 804e8fe7 Antony Chazapis
102 2715ade4 Sofia Papagiannaki
103 02c0c3fa Antony Chazapis
def printable_header_dict(d):
104 b956618e Antony Chazapis
    """Format a meta dictionary for printing out json/xml.
105 2715ade4 Sofia Papagiannaki

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

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

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

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

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

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

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