Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ 87835e94

History | View | Annotate | Download (41.7 kB)

1 2e662088 Antony Chazapis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 2715ade4 Sofia Papagiannaki
#
3 5635f9ef Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 5635f9ef Antony Chazapis
# without modification, are permitted provided that the following
5 5635f9ef Antony Chazapis
# conditions are met:
6 2715ade4 Sofia Papagiannaki
#
7 5635f9ef Antony Chazapis
#   1. Redistributions of source code must retain the above
8 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
9 5635f9ef Antony Chazapis
#      disclaimer.
10 2715ade4 Sofia Papagiannaki
#
11 5635f9ef Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
13 5635f9ef Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 5635f9ef Antony Chazapis
#      provided with the distribution.
15 2715ade4 Sofia Papagiannaki
#
16 5635f9ef Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 5635f9ef Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 5635f9ef Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 5635f9ef Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 5635f9ef Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 5635f9ef Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 5635f9ef Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 5635f9ef Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 5635f9ef Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 5635f9ef Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 5635f9ef Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 5635f9ef Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 2715ade4 Sofia Papagiannaki
#
29 5635f9ef Antony Chazapis
# The views and conclusions contained in the software and
30 5635f9ef Antony Chazapis
# documentation are those of the authors and should not be
31 5635f9ef Antony Chazapis
# interpreted as representing official policies, either expressed
32 5635f9ef Antony Chazapis
# or implied, of GRNET S.A.
33 5635f9ef Antony Chazapis
34 b956618e Antony Chazapis
from functools import wraps
35 65bbcd43 Christos Stavrakakis
from datetime import datetime
36 9fefc052 Antony Chazapis
from urllib import quote, unquote
37 b956618e Antony Chazapis
38 48c5b124 Sofia Papagiannaki
from django.http import (HttpResponse, HttpResponseRedirect, Http404,
39 48c5b124 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 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 48c5b124 Sofia Papagiannaki
                                 COOKIE_NAME, BASE_HOST, UPDATE_MD5, LOGIN_URL)
69 d4e4e501 Christos Stavrakakis
70 02de6286 Sofia Papagiannaki
from pithos.api.resources import resources
71 74cbb94a Sofia Papagiannaki
from pithos.backends import connect_backend
72 32454501 Sofia Papagiannaki
from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists,
73 32454501 Sofia Papagiannaki
                                  VersionNotExists)
74 a3fcee5b Sofia Papagiannaki
75 a3fcee5b Sofia Papagiannaki
from synnefo.lib import join_urls
76 a3fcee5b Sofia Papagiannaki
77 e6fb591c Ilias Tsitsimpis
from astakosclient import AstakosClient
78 e6fb591c Ilias Tsitsimpis
from astakosclient.errors import NoUserName, NoUUID
79 b956618e Antony Chazapis
80 b956618e Antony Chazapis
import logging
81 22dab079 Antony Chazapis
import re
82 cbfb6636 Sofia Papagiannaki
import hashlib
83 7bef5750 Antony Chazapis
import uuid
84 c48acbfd Sofia Papagiannaki
import decimal
85 8c793655 Antony Chazapis
86 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
87 b956618e Antony Chazapis
88 b956618e Antony Chazapis
89 c48acbfd Sofia Papagiannaki
def json_encode_decimal(obj):
90 c48acbfd Sofia Papagiannaki
    if isinstance(obj, decimal.Decimal):
91 c48acbfd Sofia Papagiannaki
        return str(obj)
92 c48acbfd Sofia Papagiannaki
    raise TypeError(repr(obj) + " is not JSON serializable")
93 c48acbfd Sofia Papagiannaki
94 2715ade4 Sofia Papagiannaki
95 804e8fe7 Antony Chazapis
def rename_meta_key(d, old, new):
96 804e8fe7 Antony Chazapis
    if old not in d:
97 804e8fe7 Antony Chazapis
        return
98 804e8fe7 Antony Chazapis
    d[new] = d[old]
99 804e8fe7 Antony Chazapis
    del(d[old])
100 804e8fe7 Antony Chazapis
101 2715ade4 Sofia Papagiannaki
102 02c0c3fa Antony Chazapis
def printable_header_dict(d):
103 b956618e Antony Chazapis
    """Format a meta dictionary for printing out json/xml.
104 2715ade4 Sofia Papagiannaki

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

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

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

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

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

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

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