Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ 74cbb94a

History | View | Annotate | Download (42.6 kB)

1 2e662088 Antony Chazapis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 2715ade4 Sofia Papagiannaki
#
3 5635f9ef Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 5635f9ef Antony Chazapis
# without modification, are permitted provided that the following
5 5635f9ef Antony Chazapis
# conditions are met:
6 2715ade4 Sofia Papagiannaki
#
7 5635f9ef Antony Chazapis
#   1. Redistributions of source code must retain the above
8 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
9 5635f9ef Antony Chazapis
#      disclaimer.
10 2715ade4 Sofia Papagiannaki
#
11 5635f9ef Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
13 5635f9ef Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 5635f9ef Antony Chazapis
#      provided with the distribution.
15 2715ade4 Sofia Papagiannaki
#
16 5635f9ef Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 5635f9ef Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 5635f9ef Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 5635f9ef Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 5635f9ef Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 5635f9ef Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 5635f9ef Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 5635f9ef Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 5635f9ef Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 5635f9ef Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 5635f9ef Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 5635f9ef Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 2715ade4 Sofia Papagiannaki
#
29 5635f9ef Antony Chazapis
# The views and conclusions contained in the software and
30 5635f9ef Antony Chazapis
# documentation are those of the authors and should not be
31 5635f9ef Antony Chazapis
# interpreted as representing official policies, either expressed
32 5635f9ef Antony Chazapis
# or implied, of GRNET S.A.
33 5635f9ef Antony Chazapis
34 b956618e Antony Chazapis
from functools import wraps
35 65bbcd43 Christos Stavrakakis
from datetime import datetime
36 9fefc052 Antony Chazapis
from urllib import quote, unquote
37 b956618e Antony Chazapis
38 d2d09227 Sofia Papagiannaki
from django.http import HttpResponse, Http404, HttpResponseForbidden
39 6b6b6c1e Antony Chazapis
from django.template.loader import render_to_string
40 1993fea9 Antony Chazapis
from django.utils import simplejson as json
41 22dab079 Antony Chazapis
from django.utils.http import http_date, parse_etags
42 fb064032 Antony Chazapis
from django.utils.encoding import smart_unicode, smart_str
43 817890f2 Antony Chazapis
from django.core.files.uploadhandler import FileUploadHandler
44 817890f2 Antony Chazapis
from django.core.files.uploadedfile import UploadedFile
45 a3fcee5b Sofia Papagiannaki
from django.core.urlresolvers import reverse
46 b956618e Antony Chazapis
47 896754a6 Christos Stavrakakis
from snf_django.lib.api.parsedate import parse_http_date_safe, parse_http_date
48 65bbcd43 Christos Stavrakakis
from snf_django.lib import api
49 65bbcd43 Christos Stavrakakis
from snf_django.lib.api import faults, utils
50 5a96180b Antony Chazapis
51 a7dff008 Antony Chazapis
from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION,
52 761c2b3c root
                                 BACKEND_BLOCK_MODULE, BACKEND_BLOCK_PATH,
53 761c2b3c root
                                 BACKEND_BLOCK_UMASK,
54 f4fbb0fa Sofia Papagiannaki
                                 BACKEND_QUEUE_MODULE, BACKEND_QUEUE_HOSTS,
55 b17e5550 Giorgos Korfiatis
                                 BACKEND_QUEUE_EXCHANGE,
56 16f2673e Sofia Papagiannaki
                                 ASTAKOSCLIENT_POOLSIZE,
57 b17e5550 Giorgos Korfiatis
                                 SERVICE_TOKEN,
58 e3ff6830 Georgios D. Tsoukalas
                                 ASTAKOS_BASE_URL,
59 19ddd41b Sofia Papagiannaki
                                 BACKEND_ACCOUNT_QUOTA, BACKEND_CONTAINER_QUOTA,
60 19ddd41b Sofia Papagiannaki
                                 BACKEND_VERSIONING,
61 74cbb94a Sofia Papagiannaki
                                 BACKEND_FREE_VERSIONING,
62 74cbb94a Sofia Papagiannaki
                                 BACKEND_POOL_ENABLED, BACKEND_POOL_SIZE,
63 369a7b41 Sofia Papagiannaki
                                 BACKEND_BLOCK_SIZE, BACKEND_HASH_ALGORITHM,
64 47462eda Filippos Giannakos
                                 RADOS_STORAGE, RADOS_POOL_BLOCKS,
65 56f3c759 Sofia Papagiannaki
                                 RADOS_POOL_MAPS, TRANSLATE_UUIDS,
66 133e3fcf Sofia Papagiannaki
                                 PUBLIC_URL_SECURITY, PUBLIC_URL_ALPHABET,
67 133e3fcf Sofia Papagiannaki
                                 COOKIE_NAME, BASE_HOST, UPDATE_MD5)
68 02de6286 Sofia Papagiannaki
from pithos.api.resources import resources
69 74cbb94a Sofia Papagiannaki
from pithos.backends import connect_backend
70 32454501 Sofia Papagiannaki
from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists,
71 32454501 Sofia Papagiannaki
                                  VersionNotExists)
72 a3fcee5b Sofia Papagiannaki
73 a3fcee5b Sofia Papagiannaki
from synnefo.lib import join_urls
74 a3fcee5b Sofia Papagiannaki
75 e6fb591c Ilias Tsitsimpis
from astakosclient import AstakosClient
76 e6fb591c Ilias Tsitsimpis
from astakosclient.errors import NoUserName, NoUUID
77 b956618e Antony Chazapis
78 b956618e Antony Chazapis
import logging
79 22dab079 Antony Chazapis
import re
80 cbfb6636 Sofia Papagiannaki
import hashlib
81 7bef5750 Antony Chazapis
import uuid
82 c48acbfd Sofia Papagiannaki
import decimal
83 8c793655 Antony Chazapis
84 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
85 b956618e Antony Chazapis
86 b956618e Antony Chazapis
87 c48acbfd Sofia Papagiannaki
def json_encode_decimal(obj):
88 c48acbfd Sofia Papagiannaki
    if isinstance(obj, decimal.Decimal):
89 c48acbfd Sofia Papagiannaki
        return str(obj)
90 c48acbfd Sofia Papagiannaki
    raise TypeError(repr(obj) + " is not JSON serializable")
91 c48acbfd Sofia Papagiannaki
92 2715ade4 Sofia Papagiannaki
93 804e8fe7 Antony Chazapis
def rename_meta_key(d, old, new):
94 804e8fe7 Antony Chazapis
    if old not in d:
95 804e8fe7 Antony Chazapis
        return
96 804e8fe7 Antony Chazapis
    d[new] = d[old]
97 804e8fe7 Antony Chazapis
    del(d[old])
98 804e8fe7 Antony Chazapis
99 2715ade4 Sofia Papagiannaki
100 02c0c3fa Antony Chazapis
def printable_header_dict(d):
101 b956618e Antony Chazapis
    """Format a meta dictionary for printing out json/xml.
102 2715ade4 Sofia Papagiannaki

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

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

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

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

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

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

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