Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ 0c6ab9df

History | View | Annotate | Download (46.1 kB)

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

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

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

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

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

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

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

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

1131 46662ee6 Kostas Papadimitriou
    e.g.
1132 46662ee6 Kostas Papadimitriou
    @restrict_to_host('files.example.com')
1133 46662ee6 Kostas Papadimitriou
    my_restricted_view(request, path):
1134 46662ee6 Kostas Papadimitriou
        return HttpResponse(file(path).read())
1135 46662ee6 Kostas Papadimitriou

1136 46662ee6 Kostas Papadimitriou
    A get to ``https://api.example.com/my_restricted_view/file_path/?param=1``
1137 46662ee6 Kostas Papadimitriou
    will return a redirect response with Location header set to
1138 46662ee6 Kostas Papadimitriou
    ``https://files.example.com/my_restricted_view/file_path/?param=1``.
1139 46662ee6 Kostas Papadimitriou

1140 46662ee6 Kostas Papadimitriou
    If host is set to ``None`` no restriction will be applied.
1141 46662ee6 Kostas Papadimitriou
    """
1142 46662ee6 Kostas Papadimitriou
    def decorator(func):
1143 46662ee6 Kostas Papadimitriou
        # skip decoration if no host is set
1144 46662ee6 Kostas Papadimitriou
        if not host:
1145 46662ee6 Kostas Papadimitriou
            return func
1146 46662ee6 Kostas Papadimitriou
1147 46662ee6 Kostas Papadimitriou
        @wraps(func)
1148 46662ee6 Kostas Papadimitriou
        def wrapper(request, *args, **kwargs):
1149 46662ee6 Kostas Papadimitriou
            request_host = request.get_host()
1150 46662ee6 Kostas Papadimitriou
            if host != request_host:
1151 46662ee6 Kostas Papadimitriou
                proto = 'https' if request.is_secure() else 'http'
1152 46662ee6 Kostas Papadimitriou
                if request.method in ['GET', 'HEAD']:
1153 46662ee6 Kostas Papadimitriou
                    full_path = request.get_full_path()
1154 46662ee6 Kostas Papadimitriou
                    redirect_uri = "%s://%s%s" % (proto, host, full_path)
1155 46662ee6 Kostas Papadimitriou
                    return HttpResponseRedirect(redirect_uri)
1156 46662ee6 Kostas Papadimitriou
                else:
1157 46662ee6 Kostas Papadimitriou
                    raise PermissionDenied
1158 46662ee6 Kostas Papadimitriou
            return func(request, *args, **kwargs)
1159 46662ee6 Kostas Papadimitriou
        return wrapper
1160 46662ee6 Kostas Papadimitriou
    return decorator
1161 46662ee6 Kostas Papadimitriou
1162 46662ee6 Kostas Papadimitriou
1163 63de12cf Sofia Papagiannaki
def view_method():
1164 63de12cf Sofia Papagiannaki
    """Decorator function for views."""
1165 63de12cf Sofia Papagiannaki
1166 63de12cf Sofia Papagiannaki
    def decorator(func):
1167 4bf0ab85 Sofia Papagiannaki
        @restrict_to_host(UNSAFE_DOMAIN)
1168 63de12cf Sofia Papagiannaki
        @wraps(func)
1169 63de12cf Sofia Papagiannaki
        def wrapper(request, *args, **kwargs):
1170 46662ee6 Kostas Papadimitriou
            if request.method not in ['GET', 'HEAD']:
1171 46662ee6 Kostas Papadimitriou
                return HttpResponseNotAllowed(['GET', 'HEAD'])
1172 5ae33f6c Sofia Papagiannaki
1173 8f2eb016 Sofia Papagiannaki
            try:
1174 8f2eb016 Sofia Papagiannaki
                access_token = request.GET.get('access_token')
1175 ae73cdc0 Sofia Papagiannaki
                requested_resource = text.uenc(request.path.split(VIEW_PREFIX,
1176 ae73cdc0 Sofia Papagiannaki
                                                                  2)[-1])
1177 8f2eb016 Sofia Papagiannaki
                astakos = AstakosClient(SERVICE_TOKEN, ASTAKOS_AUTH_URL,
1178 8f2eb016 Sofia Papagiannaki
                                        retry=2, use_pool=True,
1179 8f2eb016 Sofia Papagiannaki
                                        logger=logger)
1180 8f2eb016 Sofia Papagiannaki
                if access_token is not None:
1181 5ae33f6c Sofia Papagiannaki
                    # authenticate using the short-term access token
1182 b32183c6 Sofia Papagiannaki
                    try:
1183 b32183c6 Sofia Papagiannaki
                        request.user = astakos.validate_token(
1184 b32183c6 Sofia Papagiannaki
                            access_token, requested_resource)
1185 256213e4 Sofia Papagiannaki
                    except AstakosClientException:
1186 b32183c6 Sofia Papagiannaki
                        return HttpResponseRedirect(request.path)
1187 8f2eb016 Sofia Papagiannaki
                    request.user_uniq = request.user["access"]["user"]["id"]
1188 8f2eb016 Sofia Papagiannaki
1189 5ae33f6c Sofia Papagiannaki
                    _func = api_method(token_required=False,
1190 5ae33f6c Sofia Papagiannaki
                                       user_required=False)(func)
1191 5ae33f6c Sofia Papagiannaki
                    response = _func(request, *args, **kwargs)
1192 8f2eb016 Sofia Papagiannaki
                    if response.status_code == 404:
1193 8f2eb016 Sofia Papagiannaki
                        raise Http404
1194 b32183c6 Sofia Papagiannaki
                    elif response.status_code == 403:
1195 8f2eb016 Sofia Papagiannaki
                        raise PermissionDenied
1196 8f2eb016 Sofia Papagiannaki
                    return response
1197 8f2eb016 Sofia Papagiannaki
1198 fe7d0186 Sofia Papagiannaki
                client_id, client_secret = OAUTH2_CLIENT_CREDENTIALS
1199 8f2eb016 Sofia Papagiannaki
                # TODO: check if client credentials are not set
1200 8f2eb016 Sofia Papagiannaki
                authorization_code = request.GET.get('code')
1201 8c7e1398 Sofia Papagiannaki
                redirect_uri = unquote(request.build_absolute_uri(
1202 8c7e1398 Sofia Papagiannaki
                    request.get_full_path()))
1203 8f2eb016 Sofia Papagiannaki
                if authorization_code is None:
1204 5ae33f6c Sofia Papagiannaki
                    # request authorization code
1205 8f2eb016 Sofia Papagiannaki
                    params = {'response_type': 'code',
1206 8f2eb016 Sofia Papagiannaki
                              'client_id': client_id,
1207 8c7e1398 Sofia Papagiannaki
                              'redirect_uri': redirect_uri,
1208 5ae33f6c Sofia Papagiannaki
                              'state': '',  # TODO include state for security
1209 ae73cdc0 Sofia Papagiannaki
                              'scope': requested_resource}
1210 8f2eb016 Sofia Papagiannaki
                    return HttpResponseRedirect('%s?%s' %
1211 fe7d0186 Sofia Papagiannaki
                                                (join_urls(astakos.oauth2_url,
1212 fcd85f90 Sofia Papagiannaki
                                                           'auth'),
1213 8f2eb016 Sofia Papagiannaki
                                                 urlencode(params)))
1214 8f2eb016 Sofia Papagiannaki
                else:
1215 e7f0ec5e Sofia Papagiannaki
                    # request short-term access token
1216 8c7e1398 Sofia Papagiannaki
                    parts = list(urlsplit(redirect_uri))
1217 8c7e1398 Sofia Papagiannaki
                    params = dict(parse_qsl(parts[3], keep_blank_values=True))
1218 8c7e1398 Sofia Papagiannaki
                    if 'code' in params:  # always True
1219 8c7e1398 Sofia Papagiannaki
                        del params['code']
1220 8c7e1398 Sofia Papagiannaki
                    if 'state' in params:
1221 8c7e1398 Sofia Papagiannaki
                        del params['state']
1222 8c7e1398 Sofia Papagiannaki
                    parts[3] = urlencode(params)
1223 8c7e1398 Sofia Papagiannaki
                    redirect_uri = urlunsplit(parts)
1224 5ae33f6c Sofia Papagiannaki
                    data = astakos.get_token('authorization_code',
1225 fe7d0186 Sofia Papagiannaki
                                             *OAUTH2_CLIENT_CREDENTIALS,
1226 8f2eb016 Sofia Papagiannaki
                                             redirect_uri=redirect_uri,
1227 8f2eb016 Sofia Papagiannaki
                                             scope=requested_resource,
1228 8f2eb016 Sofia Papagiannaki
                                             code=authorization_code)
1229 8c7e1398 Sofia Papagiannaki
                    params['access_token'] = data.get('access_token', '')
1230 8c7e1398 Sofia Papagiannaki
                    parts[3] = urlencode(params)
1231 8c7e1398 Sofia Papagiannaki
                    redirect_uri = urlunsplit(parts)
1232 8c7e1398 Sofia Papagiannaki
                    return HttpResponseRedirect(redirect_uri)
1233 5ae33f6c Sofia Papagiannaki
            except AstakosClientException, err:
1234 5ae33f6c Sofia Papagiannaki
                logger.exception(err)
1235 8f2eb016 Sofia Papagiannaki
                raise PermissionDenied
1236 63de12cf Sofia Papagiannaki
        return wrapper
1237 63de12cf Sofia Papagiannaki
    return decorator
1238 133e3fcf Sofia Papagiannaki
1239 133e3fcf Sofia Papagiannaki
1240 133e3fcf Sofia Papagiannaki
class Checksum:
1241 133e3fcf Sofia Papagiannaki
    def __init__(self):
1242 133e3fcf Sofia Papagiannaki
        self.md5 = hashlib.md5()
1243 133e3fcf Sofia Papagiannaki
1244 133e3fcf Sofia Papagiannaki
    def update(self, data):
1245 133e3fcf Sofia Papagiannaki
        self.md5.update(data)
1246 133e3fcf Sofia Papagiannaki
1247 133e3fcf Sofia Papagiannaki
    def hexdigest(self):
1248 133e3fcf Sofia Papagiannaki
        return self.md5.hexdigest().lower()
1249 133e3fcf Sofia Papagiannaki
1250 cc62d2ab Sofia Papagiannaki
1251 133e3fcf Sofia Papagiannaki
class NoChecksum:
1252 133e3fcf Sofia Papagiannaki
    def update(self, data):
1253 133e3fcf Sofia Papagiannaki
        pass
1254 133e3fcf Sofia Papagiannaki
1255 133e3fcf Sofia Papagiannaki
    def hexdigest(self):
1256 133e3fcf Sofia Papagiannaki
        return ''