Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ 657f330d

History | View | Annotate | Download (45.4 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 fac5f6be Chrysostomos Nanakos
                                  VersionNotExists, IllegalOperationError)
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 469d0997 Georgios D. Tsoukalas
def put_object_headers(response, meta, restricted=False, token=None):
229 133e3fcf Sofia Papagiannaki
    response['ETag'] = meta['hash'] if not UPDATE_MD5 else meta['checksum']
230 b956618e Antony Chazapis
    response['Content-Length'] = meta['bytes']
231 8e95eb05 Sofia Papagiannaki
    response.override_serialization = True
232 66ce2ca5 Antony Chazapis
    response['Content-Type'] = meta.get('type', 'application/octet-stream')
233 b956618e Antony Chazapis
    response['Last-Modified'] = http_date(int(meta['modified']))
234 3ab38c43 Antony Chazapis
    if not restricted:
235 4a1c29ea Antony Chazapis
        response['X-Object-Hash'] = meta['hash']
236 37bee317 Antony Chazapis
        response['X-Object-UUID'] = meta['uuid']
237 469d0997 Georgios D. Tsoukalas
        if TRANSLATE_UUIDS:
238 ad0efdb3 Ilias Tsitsimpis
            meta['modified_by'] = \
239 ad0efdb3 Ilias Tsitsimpis
                retrieve_displayname(token, meta['modified_by'])
240 7273ee62 Sofia Papagiannaki
        response['X-Object-Modified-By'] = smart_str(
241 7273ee62 Sofia Papagiannaki
            meta['modified_by'], strings_only=True)
242 7bef5750 Antony Chazapis
        response['X-Object-Version'] = meta['version']
243 2715ade4 Sofia Papagiannaki
        response['X-Object-Version-Timestamp'] = http_date(
244 2715ade4 Sofia Papagiannaki
            int(meta['version_timestamp']))
245 7bef5750 Antony Chazapis
        for k in [x for x in meta.keys() if x.startswith('X-Object-Meta-')]:
246 2715ade4 Sofia Papagiannaki
            response[smart_str(
247 2715ade4 Sofia Papagiannaki
                k, strings_only=True)] = smart_str(meta[k], strings_only=True)
248 2715ade4 Sofia Papagiannaki
        for k in (
249 2715ade4 Sofia Papagiannaki
            'Content-Encoding', 'Content-Disposition', 'X-Object-Manifest',
250 2715ade4 Sofia Papagiannaki
            'X-Object-Sharing', 'X-Object-Shared-By', 'X-Object-Allowed-To',
251 2715ade4 Sofia Papagiannaki
                'X-Object-Public'):
252 7bef5750 Antony Chazapis
            if k in meta:
253 e7b51248 Sofia Papagiannaki
                response[k] = smart_str(meta[k], strings_only=True)
254 7bef5750 Antony Chazapis
    else:
255 c9af0703 Antony Chazapis
        for k in ('Content-Encoding', 'Content-Disposition'):
256 7bef5750 Antony Chazapis
            if k in meta:
257 e8d003e8 Antony Chazapis
                response[k] = smart_str(meta[k], strings_only=True)
258 b956618e Antony Chazapis
259 2715ade4 Sofia Papagiannaki
260 8cb45c13 Antony Chazapis
def update_manifest_meta(request, v_account, meta):
261 8cb45c13 Antony Chazapis
    """Update metadata if the object has an X-Object-Manifest."""
262 2715ade4 Sofia Papagiannaki
263 8cb45c13 Antony Chazapis
    if 'X-Object-Manifest' in meta:
264 4a1c29ea Antony Chazapis
        etag = ''
265 8cb45c13 Antony Chazapis
        bytes = 0
266 8cb45c13 Antony Chazapis
        try:
267 2715ade4 Sofia Papagiannaki
            src_container, src_name = split_container_object_string(
268 2715ade4 Sofia Papagiannaki
                '/' + meta['X-Object-Manifest'])
269 2715ade4 Sofia Papagiannaki
            objects = request.backend.list_objects(
270 2715ade4 Sofia Papagiannaki
                request.user_uniq, v_account,
271 2715ade4 Sofia Papagiannaki
                src_container, prefix=src_name, virtual=False)
272 8cb45c13 Antony Chazapis
            for x in objects:
273 133e3fcf Sofia Papagiannaki
                src_meta = request.backend.get_object_meta(
274 133e3fcf Sofia Papagiannaki
                    request.user_uniq, v_account, src_container, x[0],
275 133e3fcf Sofia Papagiannaki
                    'pithos', x[1])
276 133e3fcf Sofia Papagiannaki
                etag += (src_meta['hash'] if not UPDATE_MD5 else
277 133e3fcf Sofia Papagiannaki
                         src_meta['checksum'])
278 8cb45c13 Antony Chazapis
                bytes += src_meta['bytes']
279 8cb45c13 Antony Chazapis
        except:
280 8cb45c13 Antony Chazapis
            # Ignore errors.
281 8cb45c13 Antony Chazapis
            return
282 8cb45c13 Antony Chazapis
        meta['bytes'] = bytes
283 8cb45c13 Antony Chazapis
        md5 = hashlib.md5()
284 4a1c29ea Antony Chazapis
        md5.update(etag)
285 33b4e4a6 Antony Chazapis
        meta['checksum'] = md5.hexdigest().lower()
286 8cb45c13 Antony Chazapis
287 ad0efdb3 Ilias Tsitsimpis
288 9839cc96 root
def is_uuid(str):
289 0325be55 Sofia Papagiannaki
    if str is None:
290 0325be55 Sofia Papagiannaki
        return False
291 9839cc96 root
    try:
292 9839cc96 root
        uuid.UUID(str)
293 9839cc96 root
    except ValueError:
294 0325be55 Sofia Papagiannaki
        return False
295 9839cc96 root
    else:
296 ad0efdb3 Ilias Tsitsimpis
        return True
297 ad0efdb3 Ilias Tsitsimpis
298 2715ade4 Sofia Papagiannaki
299 27932481 Sofia Papagiannaki
##########################
300 27932481 Sofia Papagiannaki
# USER CATALOG utilities #
301 27932481 Sofia Papagiannaki
##########################
302 27932481 Sofia Papagiannaki
303 469d0997 Georgios D. Tsoukalas
def retrieve_displayname(token, uuid, fail_silently=True):
304 f759041f Ilias Tsitsimpis
    astakos = AstakosClient(token, ASTAKOS_AUTH_URL,
305 f759041f Ilias Tsitsimpis
                            retry=2, use_pool=True,
306 e3ff6830 Georgios D. Tsoukalas
                            logger=logger)
307 e6fb591c Ilias Tsitsimpis
    try:
308 f759041f Ilias Tsitsimpis
        displayname = astakos.get_username(uuid)
309 e6fb591c Ilias Tsitsimpis
    except NoUserName:
310 e6fb591c Ilias Tsitsimpis
        if not fail_silently:
311 e6fb591c Ilias Tsitsimpis
            raise ItemNotExists(uuid)
312 e6fb591c Ilias Tsitsimpis
        else:
313 e6fb591c Ilias Tsitsimpis
            # just return the uuid
314 e6fb591c Ilias Tsitsimpis
            return uuid
315 469d0997 Georgios D. Tsoukalas
    return displayname
316 32454501 Sofia Papagiannaki
317 ad0efdb3 Ilias Tsitsimpis
318 469d0997 Georgios D. Tsoukalas
def retrieve_displaynames(token, uuids, return_dict=False, fail_silently=True):
319 f759041f Ilias Tsitsimpis
    astakos = AstakosClient(token, ASTAKOS_AUTH_URL,
320 f759041f Ilias Tsitsimpis
                            retry=2, use_pool=True,
321 e3ff6830 Georgios D. Tsoukalas
                            logger=logger)
322 f759041f Ilias Tsitsimpis
    catalog = astakos.get_usernames(uuids) or {}
323 469d0997 Georgios D. Tsoukalas
    missing = list(set(uuids) - set(catalog))
324 469d0997 Georgios D. Tsoukalas
    if missing and not fail_silently:
325 469d0997 Georgios D. Tsoukalas
        raise ItemNotExists('Unknown displaynames: %s' % ', '.join(missing))
326 469d0997 Georgios D. Tsoukalas
    return catalog if return_dict else [catalog.get(i) for i in uuids]
327 88dd5c4d Sofia Papagiannaki
328 ad0efdb3 Ilias Tsitsimpis
329 27932481 Sofia Papagiannaki
def retrieve_uuid(token, displayname):
330 890c2065 Sofia Papagiannaki
    if is_uuid(displayname):
331 890c2065 Sofia Papagiannaki
        return displayname
332 890c2065 Sofia Papagiannaki
333 f759041f Ilias Tsitsimpis
    astakos = AstakosClient(token, ASTAKOS_AUTH_URL,
334 f759041f Ilias Tsitsimpis
                            retry=2, use_pool=True,
335 e3ff6830 Georgios D. Tsoukalas
                            logger=logger)
336 e6fb591c Ilias Tsitsimpis
    try:
337 f759041f Ilias Tsitsimpis
        uuid = astakos.get_uuid(displayname)
338 e6fb591c Ilias Tsitsimpis
    except NoUUID:
339 890c2065 Sofia Papagiannaki
        raise ItemNotExists(displayname)
340 890c2065 Sofia Papagiannaki
    return uuid
341 890c2065 Sofia Papagiannaki
342 ad0efdb3 Ilias Tsitsimpis
343 469d0997 Georgios D. Tsoukalas
def retrieve_uuids(token, displaynames, return_dict=False, fail_silently=True):
344 f759041f Ilias Tsitsimpis
    astakos = AstakosClient(token, ASTAKOS_AUTH_URL,
345 f759041f Ilias Tsitsimpis
                            retry=2, use_pool=True,
346 e3ff6830 Georgios D. Tsoukalas
                            logger=logger)
347 f759041f Ilias Tsitsimpis
    catalog = astakos.get_uuids(displaynames) or {}
348 469d0997 Georgios D. Tsoukalas
    missing = list(set(displaynames) - set(catalog))
349 469d0997 Georgios D. Tsoukalas
    if missing and not fail_silently:
350 469d0997 Georgios D. Tsoukalas
        raise ItemNotExists('Unknown uuids: %s' % ', '.join(missing))
351 469d0997 Georgios D. Tsoukalas
    return catalog if return_dict else [catalog.get(i) for i in displaynames]
352 890c2065 Sofia Papagiannaki
353 ad0efdb3 Ilias Tsitsimpis
354 27932481 Sofia Papagiannaki
def replace_permissions_displayname(token, holder):
355 469d0997 Georgios D. Tsoukalas
    if holder == '*':
356 469d0997 Georgios D. Tsoukalas
        return holder
357 32454501 Sofia Papagiannaki
    try:
358 32454501 Sofia Papagiannaki
        # check first for a group permission
359 4a9e3f32 Sofia Papagiannaki
        account, group = holder.split(':', 1)
360 32454501 Sofia Papagiannaki
    except ValueError:
361 469d0997 Georgios D. Tsoukalas
        return retrieve_uuid(token, holder)
362 32454501 Sofia Papagiannaki
    else:
363 469d0997 Georgios D. Tsoukalas
        return ':'.join([retrieve_uuid(token, account), group])
364 32454501 Sofia Papagiannaki
365 ad0efdb3 Ilias Tsitsimpis
366 27932481 Sofia Papagiannaki
def replace_permissions_uuid(token, holder):
367 469d0997 Georgios D. Tsoukalas
    if holder == '*':
368 469d0997 Georgios D. Tsoukalas
        return holder
369 32454501 Sofia Papagiannaki
    try:
370 32454501 Sofia Papagiannaki
        # check first for a group permission
371 4a9e3f32 Sofia Papagiannaki
        account, group = holder.split(':', 1)
372 32454501 Sofia Papagiannaki
    except ValueError:
373 469d0997 Georgios D. Tsoukalas
        return retrieve_displayname(token, holder)
374 32454501 Sofia Papagiannaki
    else:
375 469d0997 Georgios D. Tsoukalas
        return ':'.join([retrieve_displayname(token, account), group])
376 32454501 Sofia Papagiannaki
377 ad0efdb3 Ilias Tsitsimpis
378 ad0efdb3 Ilias Tsitsimpis
def update_sharing_meta(request, permissions, v_account,
379 ad0efdb3 Ilias Tsitsimpis
                        v_container, v_object, meta):
380 cca6c617 Antony Chazapis
    if permissions is None:
381 cca6c617 Antony Chazapis
        return
382 067cf1fc Antony Chazapis
    allowed, perm_path, perms = permissions
383 cca6c617 Antony Chazapis
    if len(perms) == 0:
384 cca6c617 Antony Chazapis
        return
385 32454501 Sofia Papagiannaki
386 27932481 Sofia Papagiannaki
    # replace uuid with displayname
387 469d0997 Georgios D. Tsoukalas
    if TRANSLATE_UUIDS:
388 469d0997 Georgios D. Tsoukalas
        perms['read'] = [replace_permissions_uuid(
389 ad0efdb3 Ilias Tsitsimpis
            getattr(request, 'token', None), x)
390 ad0efdb3 Ilias Tsitsimpis
            for x in perms.get('read', [])]
391 469d0997 Georgios D. Tsoukalas
        perms['write'] = [replace_permissions_uuid(
392 ad0efdb3 Ilias Tsitsimpis
            getattr(request, 'token', None), x)
393 ad0efdb3 Ilias Tsitsimpis
            for x in perms.get('write', [])]
394 32454501 Sofia Papagiannaki
395 3436eeb0 Antony Chazapis
    ret = []
396 32454501 Sofia Papagiannaki
397 cca6c617 Antony Chazapis
    r = ','.join(perms.get('read', []))
398 3436eeb0 Antony Chazapis
    if r:
399 3436eeb0 Antony Chazapis
        ret.append('read=' + r)
400 cca6c617 Antony Chazapis
    w = ','.join(perms.get('write', []))
401 3436eeb0 Antony Chazapis
    if w:
402 3436eeb0 Antony Chazapis
        ret.append('write=' + w)
403 cca6c617 Antony Chazapis
    meta['X-Object-Sharing'] = '; '.join(ret)
404 cca6c617 Antony Chazapis
    if '/'.join((v_account, v_container, v_object)) != perm_path:
405 cca6c617 Antony Chazapis
        meta['X-Object-Shared-By'] = perm_path
406 61efb530 Antony Chazapis
    if request.user_uniq != v_account:
407 067cf1fc Antony Chazapis
        meta['X-Object-Allowed-To'] = allowed
408 3436eeb0 Antony Chazapis
409 2715ade4 Sofia Papagiannaki
410 e0f916bb Antony Chazapis
def update_public_meta(public, meta):
411 e0f916bb Antony Chazapis
    if not public:
412 e0f916bb Antony Chazapis
        return
413 a3fcee5b Sofia Papagiannaki
    meta['X-Object-Public'] = join_urls(
414 cc1f91ad Sofia Papagiannaki
        BASE_HOST, reverse('pithos.api.public.public_demux', args=(public,)))
415 e0f916bb Antony Chazapis
416 2715ade4 Sofia Papagiannaki
417 b956618e Antony Chazapis
def validate_modification_preconditions(request, meta):
418 29148653 Sofia Papagiannaki
    """Check the modified timestamp conforms with the preconditions set."""
419 2715ade4 Sofia Papagiannaki
420 22dab079 Antony Chazapis
    if 'modified' not in meta:
421 2715ade4 Sofia Papagiannaki
        return  # TODO: Always return?
422 2715ade4 Sofia Papagiannaki
423 b956618e Antony Chazapis
    if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
424 b956618e Antony Chazapis
    if if_modified_since is not None:
425 b956618e Antony Chazapis
        if_modified_since = parse_http_date_safe(if_modified_since)
426 ad0efdb3 Ilias Tsitsimpis
    if (if_modified_since is not None
427 ad0efdb3 Ilias Tsitsimpis
            and int(meta['modified']) <= if_modified_since):
428 bd40abfa Christos Stavrakakis
        raise faults.NotModified('Resource has not been modified')
429 2715ade4 Sofia Papagiannaki
430 b956618e Antony Chazapis
    if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')
431 b956618e Antony Chazapis
    if if_unmodified_since is not None:
432 b956618e Antony Chazapis
        if_unmodified_since = parse_http_date_safe(if_unmodified_since)
433 ad0efdb3 Ilias Tsitsimpis
    if (if_unmodified_since is not None
434 ad0efdb3 Ilias Tsitsimpis
            and int(meta['modified']) > if_unmodified_since):
435 bd40abfa Christos Stavrakakis
        raise faults.PreconditionFailed('Resource has been modified')
436 b956618e Antony Chazapis
437 2715ade4 Sofia Papagiannaki
438 22dab079 Antony Chazapis
def validate_matching_preconditions(request, meta):
439 58a6c894 Antony Chazapis
    """Check that the ETag conforms with the preconditions set."""
440 2715ade4 Sofia Papagiannaki
441 133e3fcf Sofia Papagiannaki
    etag = meta['hash'] if not UPDATE_MD5 else meta['checksum']
442 33b4e4a6 Antony Chazapis
    if not etag:
443 33b4e4a6 Antony Chazapis
        etag = None
444 2715ade4 Sofia Papagiannaki
445 22dab079 Antony Chazapis
    if_match = request.META.get('HTTP_IF_MATCH')
446 a8326bef Antony Chazapis
    if if_match is not None:
447 4a1c29ea Antony Chazapis
        if etag is None:
448 bd40abfa Christos Stavrakakis
            raise faults.PreconditionFailed('Resource does not exist')
449 ad0efdb3 Ilias Tsitsimpis
        if (if_match != '*'
450 ad0efdb3 Ilias Tsitsimpis
                and etag not in [x.lower() for x in parse_etags(if_match)]):
451 bd40abfa Christos Stavrakakis
            raise faults.PreconditionFailed('Resource ETag does not match')
452 2715ade4 Sofia Papagiannaki
453 22dab079 Antony Chazapis
    if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
454 22dab079 Antony Chazapis
    if if_none_match is not None:
455 a8326bef Antony Chazapis
        # TODO: If this passes, must ignore If-Modified-Since header.
456 4a1c29ea Antony Chazapis
        if etag is not None:
457 29148653 Sofia Papagiannaki
            if (if_none_match == '*' or etag in [x.lower() for x in
458 29148653 Sofia Papagiannaki
                                                 parse_etags(if_none_match)]):
459 a8326bef Antony Chazapis
                # TODO: Continue if an If-Modified-Since header is present.
460 a8326bef Antony Chazapis
                if request.method in ('HEAD', 'GET'):
461 bd40abfa Christos Stavrakakis
                    raise faults.NotModified('Resource ETag matches')
462 ad0efdb3 Ilias Tsitsimpis
                raise faults.PreconditionFailed(
463 ad0efdb3 Ilias Tsitsimpis
                    'Resource exists or ETag matches')
464 22dab079 Antony Chazapis
465 2715ade4 Sofia Papagiannaki
466 83dd59c5 Antony Chazapis
def split_container_object_string(s):
467 6d817842 Antony Chazapis
    if not len(s) > 0 or s[0] != '/':
468 6d817842 Antony Chazapis
        raise ValueError
469 6d817842 Antony Chazapis
    s = s[1:]
470 8cb45c13 Antony Chazapis
    pos = s.find('/')
471 22d7b01e Antony Chazapis
    if pos == -1 or pos == len(s) - 1:
472 83dd59c5 Antony Chazapis
        raise ValueError
473 8cb45c13 Antony Chazapis
    return s[:pos], s[(pos + 1):]
474 83dd59c5 Antony Chazapis
475 2715ade4 Sofia Papagiannaki
476 ad0efdb3 Ilias Tsitsimpis
def copy_or_move_object(request, src_account, src_container, src_name,
477 ad0efdb3 Ilias Tsitsimpis
                        dest_account, dest_container, dest_name,
478 ad0efdb3 Ilias Tsitsimpis
                        move=False, delimiter=None):
479 58a6c894 Antony Chazapis
    """Copy or move an object."""
480 2715ade4 Sofia Papagiannaki
481 53cff70c Antony Chazapis
    if 'ignore_content_type' in request.GET and 'CONTENT_TYPE' in request.META:
482 53cff70c Antony Chazapis
        del(request.META['CONTENT_TYPE'])
483 66ce2ca5 Antony Chazapis
    content_type, meta, permissions, public = get_object_headers(request)
484 79bb41b7 Antony Chazapis
    src_version = request.META.get('HTTP_X_SOURCE_VERSION')
485 b956618e Antony Chazapis
    try:
486 b956618e Antony Chazapis
        if move:
487 2715ade4 Sofia Papagiannaki
            version_id = request.backend.move_object(
488 2715ade4 Sofia Papagiannaki
                request.user_uniq, src_account, src_container, src_name,
489 2715ade4 Sofia Papagiannaki
                dest_account, dest_container, dest_name,
490 2715ade4 Sofia Papagiannaki
                content_type, 'pithos', meta, False, permissions, delimiter)
491 b956618e Antony Chazapis
        else:
492 2715ade4 Sofia Papagiannaki
            version_id = request.backend.copy_object(
493 2715ade4 Sofia Papagiannaki
                request.user_uniq, src_account, src_container, src_name,
494 2715ade4 Sofia Papagiannaki
                dest_account, dest_container, dest_name,
495 ad0efdb3 Ilias Tsitsimpis
                content_type, 'pithos', meta, False, permissions,
496 ad0efdb3 Ilias Tsitsimpis
                src_version, delimiter)
497 cca6c617 Antony Chazapis
    except NotAllowedError:
498 bd40abfa Christos Stavrakakis
        raise faults.Forbidden('Not allowed')
499 7efc9f86 Sofia Papagiannaki
    except (ItemNotExists, VersionNotExists):
500 bd40abfa Christos Stavrakakis
        raise faults.ItemNotFound('Container or object does not exist')
501 3436eeb0 Antony Chazapis
    except ValueError:
502 bd40abfa Christos Stavrakakis
        raise faults.BadRequest('Invalid sharing header')
503 dfa2d4ba Sofia Papagiannaki
    except QuotaError, e:
504 bd40abfa Christos Stavrakakis
        raise faults.RequestEntityTooLarge('Quota error: %s' % e)
505 e0f916bb Antony Chazapis
    if public is not None:
506 e0f916bb Antony Chazapis
        try:
507 ad0efdb3 Ilias Tsitsimpis
            request.backend.update_object_public(
508 ad0efdb3 Ilias Tsitsimpis
                request.user_uniq, dest_account,
509 ad0efdb3 Ilias Tsitsimpis
                dest_container, dest_name, public)
510 e0f916bb Antony Chazapis
        except NotAllowedError:
511 bd40abfa Christos Stavrakakis
            raise faults.Forbidden('Not allowed')
512 7efc9f86 Sofia Papagiannaki
        except ItemNotExists:
513 bd40abfa Christos Stavrakakis
            raise faults.ItemNotFound('Object does not exist')
514 7dd293a0 Antony Chazapis
    return version_id
515 b956618e Antony Chazapis
516 2715ade4 Sofia Papagiannaki
517 1495b972 Antony Chazapis
def get_int_parameter(p):
518 83dd59c5 Antony Chazapis
    if p is not None:
519 58a6c894 Antony Chazapis
        try:
520 83dd59c5 Antony Chazapis
            p = int(p)
521 58a6c894 Antony Chazapis
        except ValueError:
522 58a6c894 Antony Chazapis
            return None
523 83dd59c5 Antony Chazapis
        if p < 0:
524 58a6c894 Antony Chazapis
            return None
525 83dd59c5 Antony Chazapis
    return p
526 58a6c894 Antony Chazapis
527 2715ade4 Sofia Papagiannaki
528 22dab079 Antony Chazapis
def get_content_length(request):
529 1495b972 Antony Chazapis
    content_length = get_int_parameter(request.META.get('CONTENT_LENGTH'))
530 1495b972 Antony Chazapis
    if content_length is None:
531 bd40abfa Christos Stavrakakis
        raise faults.LengthRequired('Missing or invalid Content-Length header')
532 22dab079 Antony Chazapis
    return content_length
533 22dab079 Antony Chazapis
534 2715ade4 Sofia Papagiannaki
535 22dab079 Antony Chazapis
def get_range(request, size):
536 58a6c894 Antony Chazapis
    """Parse a Range header from the request.
537 2715ade4 Sofia Papagiannaki

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

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

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

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

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

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

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

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

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