Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ 1d2af25c

History | View | Annotate | Download (45.5 kB)

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

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

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

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

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

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

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

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

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

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

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