Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ 890c2065

History | View | Annotate | Download (39.8 kB)

1 2e662088 Antony Chazapis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 2715ade4 Sofia Papagiannaki
#
3 5635f9ef Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 5635f9ef Antony Chazapis
# without modification, are permitted provided that the following
5 5635f9ef Antony Chazapis
# conditions are met:
6 2715ade4 Sofia Papagiannaki
#
7 5635f9ef Antony Chazapis
#   1. Redistributions of source code must retain the above
8 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
9 5635f9ef Antony Chazapis
#      disclaimer.
10 2715ade4 Sofia Papagiannaki
#
11 5635f9ef Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 5635f9ef Antony Chazapis
#      copyright notice, this list of conditions and the following
13 5635f9ef Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 5635f9ef Antony Chazapis
#      provided with the distribution.
15 2715ade4 Sofia Papagiannaki
#
16 5635f9ef Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 5635f9ef Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 5635f9ef Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 5635f9ef Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 5635f9ef Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 5635f9ef Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 5635f9ef Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 5635f9ef Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 5635f9ef Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 5635f9ef Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 5635f9ef Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 5635f9ef Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 2715ade4 Sofia Papagiannaki
#
29 5635f9ef Antony Chazapis
# The views and conclusions contained in the software and
30 5635f9ef Antony Chazapis
# documentation are those of the authors and should not be
31 5635f9ef Antony Chazapis
# interpreted as representing official policies, either expressed
32 5635f9ef Antony Chazapis
# or implied, of GRNET S.A.
33 5635f9ef Antony Chazapis
34 b956618e Antony Chazapis
from functools import wraps
35 b956618e Antony Chazapis
from time import time
36 b956618e Antony Chazapis
from traceback import format_exc
37 b956618e Antony Chazapis
from wsgiref.handlers import format_date_time
38 0d4ea090 Antony Chazapis
from binascii import hexlify, unhexlify
39 e11087c2 Antony Chazapis
from datetime import datetime, tzinfo, timedelta
40 9fefc052 Antony Chazapis
from urllib import quote, unquote
41 b956618e Antony Chazapis
42 b956618e Antony Chazapis
from django.conf import settings
43 b956618e Antony Chazapis
from django.http import HttpResponse
44 6b6b6c1e Antony Chazapis
from django.template.loader import render_to_string
45 1993fea9 Antony Chazapis
from django.utils import simplejson as json
46 22dab079 Antony Chazapis
from django.utils.http import http_date, parse_etags
47 fb064032 Antony Chazapis
from django.utils.encoding import smart_unicode, smart_str
48 817890f2 Antony Chazapis
from django.core.files.uploadhandler import FileUploadHandler
49 817890f2 Antony Chazapis
from django.core.files.uploadedfile import UploadedFile
50 b956618e Antony Chazapis
51 6e147ecc Antony Chazapis
from synnefo.lib.parsedate import parse_http_date_safe, parse_http_date
52 dfdf4802 Sofia Papagiannaki
from synnefo.lib.astakos import get_user
53 5a96180b Antony Chazapis
54 2715ade4 Sofia Papagiannaki
from pithos.api.faults import (
55 2715ade4 Sofia Papagiannaki
    Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound,
56 2715ade4 Sofia Papagiannaki
    Conflict, LengthRequired, PreconditionFailed, RequestEntityTooLarge,
57 2715ade4 Sofia Papagiannaki
    RangeNotSatisfiable, InternalServerError, NotImplemented)
58 bb4eafc6 Antony Chazapis
from pithos.api.short_url import encode_url
59 a7dff008 Antony Chazapis
from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION,
60 761c2b3c root
                                 BACKEND_BLOCK_MODULE, BACKEND_BLOCK_PATH,
61 761c2b3c root
                                 BACKEND_BLOCK_UMASK,
62 f4fbb0fa Sofia Papagiannaki
                                 BACKEND_QUEUE_MODULE, BACKEND_QUEUE_HOSTS,
63 f4fbb0fa Sofia Papagiannaki
                                 BACKEND_QUEUE_EXCHANGE,
64 3173699c Sofia Papagiannaki
                                 QUOTAHOLDER_URL, QUOTAHOLDER_TOKEN,
65 3173699c Sofia Papagiannaki
                                 BACKEND_QUOTA, BACKEND_VERSIONING,
66 b1dadd0e Sofia Papagiannaki
                                 BACKEND_FREE_VERSIONING,
67 761c2b3c root
                                 AUTHENTICATION_URL, AUTHENTICATION_USERS,
68 890c2065 Sofia Papagiannaki
                                 SERVICE_TOKEN, COOKIE_NAME, USER_CATALOG_URL,
69 47462eda Filippos Giannakos
                                 RADOS_STORAGE, RADOS_POOL_BLOCKS,
70 47462eda Filippos Giannakos
                                 RADOS_POOL_MAPS)
71 39593b2b Giorgos Verigakis
from pithos.backends import connect_backend
72 32454501 Sofia Papagiannaki
from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists,
73 32454501 Sofia Papagiannaki
                                  VersionNotExists)
74 890c2065 Sofia Papagiannaki
from synnefo.lib.astakos import (get_user_uuid, get_displayname,
75 890c2065 Sofia Papagiannaki
                                 get_uuids, get_displaynames)
76 b956618e Antony Chazapis
77 b956618e Antony Chazapis
import logging
78 22dab079 Antony Chazapis
import re
79 cbfb6636 Sofia Papagiannaki
import hashlib
80 7bef5750 Antony Chazapis
import uuid
81 c48acbfd Sofia Papagiannaki
import decimal
82 8c793655 Antony Chazapis
83 b956618e Antony Chazapis
84 b956618e Antony Chazapis
logger = logging.getLogger(__name__)
85 b956618e Antony Chazapis
86 b956618e Antony Chazapis
87 e11087c2 Antony Chazapis
class UTC(tzinfo):
88 2715ade4 Sofia Papagiannaki
    def utcoffset(self, dt):
89 2715ade4 Sofia Papagiannaki
        return timedelta(0)
90 e11087c2 Antony Chazapis
91 2715ade4 Sofia Papagiannaki
    def tzname(self, dt):
92 2715ade4 Sofia Papagiannaki
        return 'UTC'
93 e11087c2 Antony Chazapis
94 2715ade4 Sofia Papagiannaki
    def dst(self, dt):
95 2715ade4 Sofia Papagiannaki
        return timedelta(0)
96 e11087c2 Antony Chazapis
97 e11087c2 Antony Chazapis
98 c48acbfd Sofia Papagiannaki
def json_encode_decimal(obj):
99 c48acbfd Sofia Papagiannaki
    if isinstance(obj, decimal.Decimal):
100 c48acbfd Sofia Papagiannaki
        return str(obj)
101 c48acbfd Sofia Papagiannaki
    raise TypeError(repr(obj) + " is not JSON serializable")
102 c48acbfd Sofia Papagiannaki
103 2715ade4 Sofia Papagiannaki
104 e11087c2 Antony Chazapis
def isoformat(d):
105 2715ade4 Sofia Papagiannaki
    """Return an ISO8601 date string that includes a timezone."""
106 2715ade4 Sofia Papagiannaki
107 2715ade4 Sofia Papagiannaki
    return d.replace(tzinfo=UTC()).isoformat()
108 e11087c2 Antony Chazapis
109 e11087c2 Antony Chazapis
110 804e8fe7 Antony Chazapis
def rename_meta_key(d, old, new):
111 804e8fe7 Antony Chazapis
    if old not in d:
112 804e8fe7 Antony Chazapis
        return
113 804e8fe7 Antony Chazapis
    d[new] = d[old]
114 804e8fe7 Antony Chazapis
    del(d[old])
115 804e8fe7 Antony Chazapis
116 2715ade4 Sofia Papagiannaki
117 02c0c3fa Antony Chazapis
def printable_header_dict(d):
118 b956618e Antony Chazapis
    """Format a meta dictionary for printing out json/xml.
119 2715ade4 Sofia Papagiannaki

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

485 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
486 22dab079 Antony Chazapis
    or a list of (offset, length) tuples - should be further checked.
487 b956618e Antony Chazapis
    """
488 2715ade4 Sofia Papagiannaki
489 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_RANGE', '').replace(' ', '')
490 22dab079 Antony Chazapis
    if not ranges.startswith('bytes='):
491 b956618e Antony Chazapis
        return None
492 2715ade4 Sofia Papagiannaki
493 22dab079 Antony Chazapis
    ret = []
494 22dab079 Antony Chazapis
    for r in (x.strip() for x in ranges[6:].split(',')):
495 22dab079 Antony Chazapis
        p = re.compile('^(?P<offset>\d*)-(?P<upto>\d*)$')
496 22dab079 Antony Chazapis
        m = p.match(r)
497 22dab079 Antony Chazapis
        if not m:
498 22dab079 Antony Chazapis
            return None
499 22dab079 Antony Chazapis
        offset = m.group('offset')
500 22dab079 Antony Chazapis
        upto = m.group('upto')
501 22dab079 Antony Chazapis
        if offset == '' and upto == '':
502 b956618e Antony Chazapis
            return None
503 2715ade4 Sofia Papagiannaki
504 22dab079 Antony Chazapis
        if offset != '':
505 22dab079 Antony Chazapis
            offset = int(offset)
506 22dab079 Antony Chazapis
            if upto != '':
507 b956618e Antony Chazapis
                upto = int(upto)
508 22dab079 Antony Chazapis
                if offset > upto:
509 22dab079 Antony Chazapis
                    return None
510 22dab079 Antony Chazapis
                ret.append((offset, upto - offset + 1))
511 22dab079 Antony Chazapis
            else:
512 22dab079 Antony Chazapis
                ret.append((offset, size - offset))
513 b956618e Antony Chazapis
        else:
514 22dab079 Antony Chazapis
            length = int(upto)
515 22dab079 Antony Chazapis
            ret.append((size - length, length))
516 2715ade4 Sofia Papagiannaki
517 22dab079 Antony Chazapis
    return ret
518 22dab079 Antony Chazapis
519 2715ade4 Sofia Papagiannaki
520 22dab079 Antony Chazapis
def get_content_range(request):
521 58a6c894 Antony Chazapis
    """Parse a Content-Range header from the request.
522 2715ade4 Sofia Papagiannaki

523 22dab079 Antony Chazapis
    Either returns None, when the header is not existent or should be ignored,
524 22dab079 Antony Chazapis
    or an (offset, length, total) tuple - check as length, total may be None.
525 22dab079 Antony Chazapis
    Returns (None, None, None) if the provided range is '*/*'.
526 22dab079 Antony Chazapis
    """
527 2715ade4 Sofia Papagiannaki
528 22dab079 Antony Chazapis
    ranges = request.META.get('HTTP_CONTENT_RANGE', '')
529 22dab079 Antony Chazapis
    if not ranges:
530 22dab079 Antony Chazapis
        return None
531 2715ade4 Sofia Papagiannaki
532 22dab079 Antony Chazapis
    p = re.compile('^bytes (?P<offset>\d+)-(?P<upto>\d*)/(?P<total>(\d+|\*))$')
533 22dab079 Antony Chazapis
    m = p.match(ranges)
534 22dab079 Antony Chazapis
    if not m:
535 22dab079 Antony Chazapis
        if ranges == 'bytes */*':
536 22dab079 Antony Chazapis
            return (None, None, None)
537 22dab079 Antony Chazapis
        return None
538 22dab079 Antony Chazapis
    offset = int(m.group('offset'))
539 22dab079 Antony Chazapis
    upto = m.group('upto')
540 22dab079 Antony Chazapis
    total = m.group('total')
541 22dab079 Antony Chazapis
    if upto != '':
542 22dab079 Antony Chazapis
        upto = int(upto)
543 b956618e Antony Chazapis
    else:
544 22dab079 Antony Chazapis
        upto = None
545 22dab079 Antony Chazapis
    if total != '*':
546 22dab079 Antony Chazapis
        total = int(total)
547 22dab079 Antony Chazapis
    else:
548 22dab079 Antony Chazapis
        total = None
549 70e526a0 Antony Chazapis
    if (upto is not None and offset > upto) or \
550 70e526a0 Antony Chazapis
        (total is not None and offset >= total) or \
551 2715ade4 Sofia Papagiannaki
            (total is not None and upto is not None and upto >= total):
552 22dab079 Antony Chazapis
        return None
553 2715ade4 Sofia Papagiannaki
554 70e526a0 Antony Chazapis
    if upto is None:
555 22dab079 Antony Chazapis
        length = None
556 22dab079 Antony Chazapis
    else:
557 22dab079 Antony Chazapis
        length = upto - offset + 1
558 22dab079 Antony Chazapis
    return (offset, length, total)
559 b956618e Antony Chazapis
560 2715ade4 Sofia Papagiannaki
561 3436eeb0 Antony Chazapis
def get_sharing(request):
562 3436eeb0 Antony Chazapis
    """Parse an X-Object-Sharing header from the request.
563 2715ade4 Sofia Papagiannaki

564 3436eeb0 Antony Chazapis
    Raises BadRequest on error.
565 3436eeb0 Antony Chazapis
    """
566 2715ade4 Sofia Papagiannaki
567 3436eeb0 Antony Chazapis
    permissions = request.META.get('HTTP_X_OBJECT_SHARING')
568 cca6c617 Antony Chazapis
    if permissions is None:
569 3436eeb0 Antony Chazapis
        return None
570 2715ade4 Sofia Papagiannaki
571 0a7f1671 Antony Chazapis
    # TODO: Document or remove '~' replacing.
572 0a7f1671 Antony Chazapis
    permissions = permissions.replace('~', '')
573 2715ade4 Sofia Papagiannaki
574 3436eeb0 Antony Chazapis
    ret = {}
575 cca6c617 Antony Chazapis
    permissions = permissions.replace(' ', '')
576 cca6c617 Antony Chazapis
    if permissions == '':
577 cca6c617 Antony Chazapis
        return ret
578 cca6c617 Antony Chazapis
    for perm in (x for x in permissions.split(';')):
579 cca6c617 Antony Chazapis
        if perm.startswith('read='):
580 2715ade4 Sofia Papagiannaki
            ret['read'] = list(set(
581 2715ade4 Sofia Papagiannaki
                [v.replace(' ', '').lower() for v in perm[5:].split(',')]))
582 02c0c3fa Antony Chazapis
            if '' in ret['read']:
583 02c0c3fa Antony Chazapis
                ret['read'].remove('')
584 e8886082 Antony Chazapis
            if '*' in ret['read']:
585 e8886082 Antony Chazapis
                ret['read'] = ['*']
586 3436eeb0 Antony Chazapis
            if len(ret['read']) == 0:
587 32454501 Sofia Papagiannaki
                raise BadRequest(
588 32454501 Sofia Papagiannaki
                    'Bad X-Object-Sharing header value: invalid length')
589 3436eeb0 Antony Chazapis
        elif perm.startswith('write='):
590 2715ade4 Sofia Papagiannaki
            ret['write'] = list(set(
591 2715ade4 Sofia Papagiannaki
                [v.replace(' ', '').lower() for v in perm[6:].split(',')]))
592 02c0c3fa Antony Chazapis
            if '' in ret['write']:
593 02c0c3fa Antony Chazapis
                ret['write'].remove('')
594 e8886082 Antony Chazapis
            if '*' in ret['write']:
595 e8886082 Antony Chazapis
                ret['write'] = ['*']
596 3436eeb0 Antony Chazapis
            if len(ret['write']) == 0:
597 32454501 Sofia Papagiannaki
                raise BadRequest(
598 32454501 Sofia Papagiannaki
                    'Bad X-Object-Sharing header value: invalid length')
599 3436eeb0 Antony Chazapis
        else:
600 32454501 Sofia Papagiannaki
            raise BadRequest(
601 32454501 Sofia Papagiannaki
                'Bad X-Object-Sharing header value: missing prefix')
602 32454501 Sofia Papagiannaki
603 890c2065 Sofia Papagiannaki
    # replace displayname with uuid
604 32454501 Sofia Papagiannaki
    try:
605 32454501 Sofia Papagiannaki
        ret['read'] = \
606 890c2065 Sofia Papagiannaki
            [replace_permissions_displayname(x) for x in ret.get('read', [])]
607 32454501 Sofia Papagiannaki
        ret['write'] = \
608 890c2065 Sofia Papagiannaki
            [replace_permissions_displayname(x) for x in ret.get('write', [])]
609 874cddc7 Sofia Papagiannaki
    except ItemNotExists, e:
610 32454501 Sofia Papagiannaki
        raise BadRequest(
611 32454501 Sofia Papagiannaki
            'Bad X-Object-Sharing header value: unknown account: %s' % e)
612 2715ade4 Sofia Papagiannaki
613 21a8a6ff Antony Chazapis
    # Keep duplicates only in write list.
614 2715ade4 Sofia Papagiannaki
    dups = [x for x in ret.get(
615 2715ade4 Sofia Papagiannaki
        'read', []) if x in ret.get('write', []) and x != '*']
616 21a8a6ff Antony Chazapis
    if dups:
617 21a8a6ff Antony Chazapis
        for x in dups:
618 21a8a6ff Antony Chazapis
            ret['read'].remove(x)
619 21a8a6ff Antony Chazapis
        if len(ret['read']) == 0:
620 21a8a6ff Antony Chazapis
            del(ret['read'])
621 2715ade4 Sofia Papagiannaki
622 3436eeb0 Antony Chazapis
    return ret
623 3436eeb0 Antony Chazapis
624 2715ade4 Sofia Papagiannaki
625 3ab38c43 Antony Chazapis
def get_public(request):
626 3ab38c43 Antony Chazapis
    """Parse an X-Object-Public header from the request.
627 2715ade4 Sofia Papagiannaki

628 3ab38c43 Antony Chazapis
    Raises BadRequest on error.
629 3ab38c43 Antony Chazapis
    """
630 2715ade4 Sofia Papagiannaki
631 3ab38c43 Antony Chazapis
    public = request.META.get('HTTP_X_OBJECT_PUBLIC')
632 3ab38c43 Antony Chazapis
    if public is None:
633 3ab38c43 Antony Chazapis
        return None
634 2715ade4 Sofia Papagiannaki
635 3ab38c43 Antony Chazapis
    public = public.replace(' ', '').lower()
636 3ab38c43 Antony Chazapis
    if public == 'true':
637 3ab38c43 Antony Chazapis
        return True
638 3ab38c43 Antony Chazapis
    elif public == 'false' or public == '':
639 3ab38c43 Antony Chazapis
        return False
640 3ab38c43 Antony Chazapis
    raise BadRequest('Bad X-Object-Public header value')
641 3ab38c43 Antony Chazapis
642 2715ade4 Sofia Papagiannaki
643 b956618e Antony Chazapis
def raw_input_socket(request):
644 58a6c894 Antony Chazapis
    """Return the socket for reading the rest of the request."""
645 2715ade4 Sofia Papagiannaki
646 b956618e Antony Chazapis
    server_software = request.META.get('SERVER_SOFTWARE')
647 fc1b2a75 Antony Chazapis
    if server_software and server_software.startswith('mod_python'):
648 b956618e Antony Chazapis
        return request._req
649 fc1b2a75 Antony Chazapis
    if 'wsgi.input' in request.environ:
650 fc1b2a75 Antony Chazapis
        return request.environ['wsgi.input']
651 08de868d Antony Chazapis
    raise NotImplemented('Unknown server software')
652 b956618e Antony Chazapis
653 2715ade4 Sofia Papagiannaki
MAX_UPLOAD_SIZE = 5 * (1024 * 1024 * 1024)  # 5GB
654 2715ade4 Sofia Papagiannaki
655 b956618e Antony Chazapis
656 c032f34d Antony Chazapis
def socket_read_iterator(request, length=0, blocksize=4096):
657 58a6c894 Antony Chazapis
    """Return a maximum of blocksize data read from the socket in each iteration.
658 2715ade4 Sofia Papagiannaki

659 22dab079 Antony Chazapis
    Read up to 'length'. If 'length' is negative, will attempt a chunked read.
660 b956618e Antony Chazapis
    The maximum ammount of data read is controlled by MAX_UPLOAD_SIZE.
661 b956618e Antony Chazapis
    """
662 2715ade4 Sofia Papagiannaki
663 c032f34d Antony Chazapis
    sock = raw_input_socket(request)
664 2715ade4 Sofia Papagiannaki
    if length < 0:  # Chunked transfers
665 c032f34d Antony Chazapis
        # Small version (server does the dechunking).
666 032dc768 Antony Chazapis
        if request.environ.get('mod_wsgi.input_chunked', None) or request.META['SERVER_SOFTWARE'].startswith('gunicorn'):
667 c032f34d Antony Chazapis
            while length < MAX_UPLOAD_SIZE:
668 c032f34d Antony Chazapis
                data = sock.read(blocksize)
669 c032f34d Antony Chazapis
                if data == '':
670 c032f34d Antony Chazapis
                    return
671 c032f34d Antony Chazapis
                yield data
672 c032f34d Antony Chazapis
            raise BadRequest('Maximum size is reached')
673 2715ade4 Sofia Papagiannaki
674 c032f34d Antony Chazapis
        # Long version (do the dechunking).
675 22dab079 Antony Chazapis
        data = ''
676 b956618e Antony Chazapis
        while length < MAX_UPLOAD_SIZE:
677 22dab079 Antony Chazapis
            # Get chunk size.
678 22dab079 Antony Chazapis
            if hasattr(sock, 'readline'):
679 22dab079 Antony Chazapis
                chunk_length = sock.readline()
680 22dab079 Antony Chazapis
            else:
681 22dab079 Antony Chazapis
                chunk_length = ''
682 22dab079 Antony Chazapis
                while chunk_length[-1:] != '\n':
683 22dab079 Antony Chazapis
                    chunk_length += sock.read(1)
684 22dab079 Antony Chazapis
                chunk_length.strip()
685 b956618e Antony Chazapis
            pos = chunk_length.find(';')
686 b956618e Antony Chazapis
            if pos >= 0:
687 b956618e Antony Chazapis
                chunk_length = chunk_length[:pos]
688 b956618e Antony Chazapis
            try:
689 b956618e Antony Chazapis
                chunk_length = int(chunk_length, 16)
690 b956618e Antony Chazapis
            except Exception, e:
691 2715ade4 Sofia Papagiannaki
                raise BadRequest('Bad chunk size')
692 2715ade4 Sofia Papagiannaki
                                 # TODO: Change to something more appropriate.
693 22dab079 Antony Chazapis
            # Check if done.
694 b956618e Antony Chazapis
            if chunk_length == 0:
695 22dab079 Antony Chazapis
                if len(data) > 0:
696 22dab079 Antony Chazapis
                    yield data
697 b956618e Antony Chazapis
                return
698 22dab079 Antony Chazapis
            # Get the actual data.
699 b956618e Antony Chazapis
            while chunk_length > 0:
700 22dab079 Antony Chazapis
                chunk = sock.read(min(chunk_length, blocksize))
701 22dab079 Antony Chazapis
                chunk_length -= len(chunk)
702 623a0cf4 Sofia Papagiannaki
                if length > 0:
703 623a0cf4 Sofia Papagiannaki
                    length += len(chunk)
704 22dab079 Antony Chazapis
                data += chunk
705 22dab079 Antony Chazapis
                if len(data) >= blocksize:
706 22dab079 Antony Chazapis
                    ret = data[:blocksize]
707 22dab079 Antony Chazapis
                    data = data[blocksize:]
708 22dab079 Antony Chazapis
                    yield ret
709 2715ade4 Sofia Papagiannaki
            sock.read(2)  # CRLF
710 32a437b1 Sofia Papagiannaki
        raise BadRequest('Maximum size is reached')
711 b956618e Antony Chazapis
    else:
712 b956618e Antony Chazapis
        if length > MAX_UPLOAD_SIZE:
713 32a437b1 Sofia Papagiannaki
            raise BadRequest('Maximum size is reached')
714 b956618e Antony Chazapis
        while length > 0:
715 b956618e Antony Chazapis
            data = sock.read(min(length, blocksize))
716 7b25e082 Antony Chazapis
            if not data:
717 7b25e082 Antony Chazapis
                raise BadRequest()
718 b956618e Antony Chazapis
            length -= len(data)
719 b956618e Antony Chazapis
            yield data
720 b956618e Antony Chazapis
721 2715ade4 Sofia Papagiannaki
722 817890f2 Antony Chazapis
class SaveToBackendHandler(FileUploadHandler):
723 817890f2 Antony Chazapis
    """Handle a file from an HTML form the django way."""
724 2715ade4 Sofia Papagiannaki
725 817890f2 Antony Chazapis
    def __init__(self, request=None):
726 817890f2 Antony Chazapis
        super(SaveToBackendHandler, self).__init__(request)
727 817890f2 Antony Chazapis
        self.backend = request.backend
728 2715ade4 Sofia Papagiannaki
729 817890f2 Antony Chazapis
    def put_data(self, length):
730 817890f2 Antony Chazapis
        if len(self.data) >= length:
731 817890f2 Antony Chazapis
            block = self.data[:length]
732 817890f2 Antony Chazapis
            self.file.hashmap.append(self.backend.put_block(block))
733 817890f2 Antony Chazapis
            self.md5.update(block)
734 817890f2 Antony Chazapis
            self.data = self.data[length:]
735 2715ade4 Sofia Papagiannaki
736 817890f2 Antony Chazapis
    def new_file(self, field_name, file_name, content_type, content_length, charset=None):
737 2715ade4 Sofia Papagiannaki
        self.md5 = hashlib.md5()
738 817890f2 Antony Chazapis
        self.data = ''
739 2715ade4 Sofia Papagiannaki
        self.file = UploadedFile(
740 2715ade4 Sofia Papagiannaki
            name=file_name, content_type=content_type, charset=charset)
741 817890f2 Antony Chazapis
        self.file.size = 0
742 817890f2 Antony Chazapis
        self.file.hashmap = []
743 2715ade4 Sofia Papagiannaki
744 817890f2 Antony Chazapis
    def receive_data_chunk(self, raw_data, start):
745 817890f2 Antony Chazapis
        self.data += raw_data
746 817890f2 Antony Chazapis
        self.file.size += len(raw_data)
747 817890f2 Antony Chazapis
        self.put_data(self.request.backend.block_size)
748 817890f2 Antony Chazapis
        return None
749 2715ade4 Sofia Papagiannaki
750 817890f2 Antony Chazapis
    def file_complete(self, file_size):
751 817890f2 Antony Chazapis
        l = len(self.data)
752 817890f2 Antony Chazapis
        if l > 0:
753 817890f2 Antony Chazapis
            self.put_data(l)
754 817890f2 Antony Chazapis
        self.file.etag = self.md5.hexdigest().lower()
755 817890f2 Antony Chazapis
        return self.file
756 817890f2 Antony Chazapis
757 2715ade4 Sofia Papagiannaki
758 22dab079 Antony Chazapis
class ObjectWrapper(object):
759 58a6c894 Antony Chazapis
    """Return the object's data block-per-block in each iteration.
760 2715ade4 Sofia Papagiannaki

761 22dab079 Antony Chazapis
    Read from the object using the offset and length provided in each entry of the range list.
762 22dab079 Antony Chazapis
    """
763 2715ade4 Sofia Papagiannaki
764 39593b2b Giorgos Verigakis
    def __init__(self, backend, ranges, sizes, hashmaps, boundary):
765 39593b2b Giorgos Verigakis
        self.backend = backend
766 22dab079 Antony Chazapis
        self.ranges = ranges
767 8cb45c13 Antony Chazapis
        self.sizes = sizes
768 8cb45c13 Antony Chazapis
        self.hashmaps = hashmaps
769 22dab079 Antony Chazapis
        self.boundary = boundary
770 8cb45c13 Antony Chazapis
        self.size = sum(self.sizes)
771 2715ade4 Sofia Papagiannaki
772 8cb45c13 Antony Chazapis
        self.file_index = 0
773 8cb45c13 Antony Chazapis
        self.block_index = 0
774 8cb45c13 Antony Chazapis
        self.block_hash = -1
775 22dab079 Antony Chazapis
        self.block = ''
776 2715ade4 Sofia Papagiannaki
777 22dab079 Antony Chazapis
        self.range_index = -1
778 22dab079 Antony Chazapis
        self.offset, self.length = self.ranges[0]
779 2715ade4 Sofia Papagiannaki
780 22dab079 Antony Chazapis
    def __iter__(self):
781 22dab079 Antony Chazapis
        return self
782 2715ade4 Sofia Papagiannaki
783 22dab079 Antony Chazapis
    def part_iterator(self):
784 22dab079 Antony Chazapis
        if self.length > 0:
785 8cb45c13 Antony Chazapis
            # Get the file for the current offset.
786 8cb45c13 Antony Chazapis
            file_size = self.sizes[self.file_index]
787 8cb45c13 Antony Chazapis
            while self.offset >= file_size:
788 8cb45c13 Antony Chazapis
                self.offset -= file_size
789 8cb45c13 Antony Chazapis
                self.file_index += 1
790 8cb45c13 Antony Chazapis
                file_size = self.sizes[self.file_index]
791 2715ade4 Sofia Papagiannaki
792 8cb45c13 Antony Chazapis
            # Get the block for the current position.
793 39593b2b Giorgos Verigakis
            self.block_index = int(self.offset / self.backend.block_size)
794 8cb45c13 Antony Chazapis
            if self.block_hash != self.hashmaps[self.file_index][self.block_index]:
795 2715ade4 Sofia Papagiannaki
                self.block_hash = self.hashmaps[
796 2715ade4 Sofia Papagiannaki
                    self.file_index][self.block_index]
797 22dab079 Antony Chazapis
                try:
798 39593b2b Giorgos Verigakis
                    self.block = self.backend.get_block(self.block_hash)
799 7efc9f86 Sofia Papagiannaki
                except ItemNotExists:
800 22dab079 Antony Chazapis
                    raise ItemNotFound('Block does not exist')
801 2715ade4 Sofia Papagiannaki
802 22dab079 Antony Chazapis
            # Get the data from the block.
803 39593b2b Giorgos Verigakis
            bo = self.offset % self.backend.block_size
804 c9865fe1 Antony Chazapis
            bs = self.backend.block_size
805 45cf0bc8 Antony Chazapis
            if (self.block_index == len(self.hashmaps[self.file_index]) - 1 and
806 2715ade4 Sofia Papagiannaki
                    self.sizes[self.file_index] % self.backend.block_size):
807 c9865fe1 Antony Chazapis
                bs = self.sizes[self.file_index] % self.backend.block_size
808 c9865fe1 Antony Chazapis
            bl = min(self.length, bs - bo)
809 22dab079 Antony Chazapis
            data = self.block[bo:bo + bl]
810 22dab079 Antony Chazapis
            self.offset += bl
811 22dab079 Antony Chazapis
            self.length -= bl
812 22dab079 Antony Chazapis
            return data
813 22dab079 Antony Chazapis
        else:
814 22dab079 Antony Chazapis
            raise StopIteration
815 2715ade4 Sofia Papagiannaki
816 22dab079 Antony Chazapis
    def next(self):
817 22dab079 Antony Chazapis
        if len(self.ranges) == 1:
818 22dab079 Antony Chazapis
            return self.part_iterator()
819 22dab079 Antony Chazapis
        if self.range_index == len(self.ranges):
820 22dab079 Antony Chazapis
            raise StopIteration
821 22dab079 Antony Chazapis
        try:
822 22dab079 Antony Chazapis
            if self.range_index == -1:
823 22dab079 Antony Chazapis
                raise StopIteration
824 22dab079 Antony Chazapis
            return self.part_iterator()
825 22dab079 Antony Chazapis
        except StopIteration:
826 22dab079 Antony Chazapis
            self.range_index += 1
827 22dab079 Antony Chazapis
            out = []
828 22dab079 Antony Chazapis
            if self.range_index < len(self.ranges):
829 22dab079 Antony Chazapis
                # Part header.
830 22dab079 Antony Chazapis
                self.offset, self.length = self.ranges[self.range_index]
831 8cb45c13 Antony Chazapis
                self.file_index = 0
832 22dab079 Antony Chazapis
                if self.range_index > 0:
833 22dab079 Antony Chazapis
                    out.append('')
834 22dab079 Antony Chazapis
                out.append('--' + self.boundary)
835 2715ade4 Sofia Papagiannaki
                out.append('Content-Range: bytes %d-%d/%d' % (
836 2715ade4 Sofia Papagiannaki
                    self.offset, self.offset + self.length - 1, self.size))
837 22dab079 Antony Chazapis
                out.append('Content-Transfer-Encoding: binary')
838 22dab079 Antony Chazapis
                out.append('')
839 22dab079 Antony Chazapis
                out.append('')
840 22dab079 Antony Chazapis
                return '\r\n'.join(out)
841 22dab079 Antony Chazapis
            else:
842 22dab079 Antony Chazapis
                # Footer.
843 22dab079 Antony Chazapis
                out.append('')
844 22dab079 Antony Chazapis
                out.append('--' + self.boundary + '--')
845 22dab079 Antony Chazapis
                out.append('')
846 22dab079 Antony Chazapis
                return '\r\n'.join(out)
847 22dab079 Antony Chazapis
848 2715ade4 Sofia Papagiannaki
849 8cb45c13 Antony Chazapis
def object_data_response(request, sizes, hashmaps, meta, public=False):
850 7bef5750 Antony Chazapis
    """Get the HttpResponse object for replying with the object's data."""
851 2715ade4 Sofia Papagiannaki
852 7bef5750 Antony Chazapis
    # Range handling.
853 8cb45c13 Antony Chazapis
    size = sum(sizes)
854 7bef5750 Antony Chazapis
    ranges = get_range(request, size)
855 7bef5750 Antony Chazapis
    if ranges is None:
856 7bef5750 Antony Chazapis
        ranges = [(0, size)]
857 7bef5750 Antony Chazapis
        ret = 200
858 7bef5750 Antony Chazapis
    else:
859 7bef5750 Antony Chazapis
        check = [True for offset, length in ranges if
860 2715ade4 Sofia Papagiannaki
                 length <= 0 or length > size or
861 2715ade4 Sofia Papagiannaki
                 offset < 0 or offset >= size or
862 2715ade4 Sofia Papagiannaki
                 offset + length > size]
863 7bef5750 Antony Chazapis
        if len(check) > 0:
864 6d1e6dce Sofia Papagiannaki
            raise RangeNotSatisfiable('Requested range exceeds object limits')
865 7bef5750 Antony Chazapis
        ret = 206
866 15d465b8 Antony Chazapis
        if_range = request.META.get('HTTP_IF_RANGE')
867 15d465b8 Antony Chazapis
        if if_range:
868 6d1e6dce Sofia Papagiannaki
            try:
869 15d465b8 Antony Chazapis
                # Modification time has passed instead.
870 6d1e6dce Sofia Papagiannaki
                last_modified = parse_http_date(if_range)
871 6d1e6dce Sofia Papagiannaki
                if last_modified != meta['modified']:
872 6d1e6dce Sofia Papagiannaki
                    ranges = [(0, size)]
873 6d1e6dce Sofia Papagiannaki
                    ret = 200
874 6d1e6dce Sofia Papagiannaki
            except ValueError:
875 33b4e4a6 Antony Chazapis
                if if_range != meta['checksum']:
876 6d1e6dce Sofia Papagiannaki
                    ranges = [(0, size)]
877 6d1e6dce Sofia Papagiannaki
                    ret = 200
878 2715ade4 Sofia Papagiannaki
879 7bef5750 Antony Chazapis
    if ret == 206 and len(ranges) > 1:
880 7bef5750 Antony Chazapis
        boundary = uuid.uuid4().hex
881 7bef5750 Antony Chazapis
    else:
882 7bef5750 Antony Chazapis
        boundary = ''
883 39593b2b Giorgos Verigakis
    wrapper = ObjectWrapper(request.backend, ranges, sizes, hashmaps, boundary)
884 7bef5750 Antony Chazapis
    response = HttpResponse(wrapper, status=ret)
885 02c0c3fa Antony Chazapis
    put_object_headers(response, meta, public)
886 7bef5750 Antony Chazapis
    if ret == 206:
887 7bef5750 Antony Chazapis
        if len(ranges) == 1:
888 7bef5750 Antony Chazapis
            offset, length = ranges[0]
889 2715ade4 Sofia Papagiannaki
            response[
890 2715ade4 Sofia Papagiannaki
                'Content-Length'] = length  # Update with the correct length.
891 2715ade4 Sofia Papagiannaki
            response['Content-Range'] = 'bytes %d-%d/%d' % (
892 2715ade4 Sofia Papagiannaki
                offset, offset + length - 1, size)
893 7bef5750 Antony Chazapis
        else:
894 7bef5750 Antony Chazapis
            del(response['Content-Length'])
895 2715ade4 Sofia Papagiannaki
            response['Content-Type'] = 'multipart/byteranges; boundary=%s' % (
896 2715ade4 Sofia Papagiannaki
                boundary,)
897 7bef5750 Antony Chazapis
    return response
898 7bef5750 Antony Chazapis
899 2715ade4 Sofia Papagiannaki
900 39593b2b Giorgos Verigakis
def put_object_block(request, hashmap, data, offset):
901 cb146cf9 Antony Chazapis
    """Put one block of data at the given offset."""
902 2715ade4 Sofia Papagiannaki
903 39593b2b Giorgos Verigakis
    bi = int(offset / request.backend.block_size)
904 39593b2b Giorgos Verigakis
    bo = offset % request.backend.block_size
905 39593b2b Giorgos Verigakis
    bl = min(len(data), request.backend.block_size - bo)
906 cb146cf9 Antony Chazapis
    if bi < len(hashmap):
907 39593b2b Giorgos Verigakis
        hashmap[bi] = request.backend.update_block(hashmap[bi], data[:bl], bo)
908 cb146cf9 Antony Chazapis
    else:
909 39593b2b Giorgos Verigakis
        hashmap.append(request.backend.put_block(('\x00' * bo) + data[:bl]))
910 2715ade4 Sofia Papagiannaki
    return bl  # Return ammount of data written.
911 2715ade4 Sofia Papagiannaki
912 cb146cf9 Antony Chazapis
913 b3155065 Antony Chazapis
def hashmap_md5(backend, hashmap, size):
914 cddcf432 chazapis
    """Produce the MD5 sum from the data in the hashmap."""
915 2715ade4 Sofia Papagiannaki
916 cddcf432 chazapis
    # TODO: Search backend for the MD5 of another object with the same hashmap and size...
917 cddcf432 chazapis
    md5 = hashlib.md5()
918 b3155065 Antony Chazapis
    bs = backend.block_size
919 cddcf432 chazapis
    for bi, hash in enumerate(hashmap):
920 2715ade4 Sofia Papagiannaki
        data = backend.get_block(hash)  # Blocks come in padded.
921 cddcf432 chazapis
        if bi == len(hashmap) - 1:
922 c9865fe1 Antony Chazapis
            data = data[:size % bs]
923 c9865fe1 Antony Chazapis
        md5.update(data)
924 cddcf432 chazapis
    return md5.hexdigest().lower()
925 49133392 Antony Chazapis
926 2715ade4 Sofia Papagiannaki
927 af7bb62f Antony Chazapis
def simple_list_response(request, l):
928 6b6b6c1e Antony Chazapis
    if request.serialization == 'text':
929 6b6b6c1e Antony Chazapis
        return '\n'.join(l) + '\n'
930 6b6b6c1e Antony Chazapis
    if request.serialization == 'xml':
931 af7bb62f Antony Chazapis
        return render_to_string('items.xml', {'items': l})
932 6b6b6c1e Antony Chazapis
    if request.serialization == 'json':
933 6b6b6c1e Antony Chazapis
        return json.dumps(l)
934 6b6b6c1e Antony Chazapis
935 35d42381 Vangelis Koukis
936 edb7875c Christos Stavrakakis
from pithos.backends.util import PithosBackendPool
937 35d42381 Vangelis Koukis
POOL_SIZE = 5
938 47462eda Filippos Giannakos
if RADOS_STORAGE:
939 47462eda Filippos Giannakos
    BLOCK_PARAMS = { 'mappool': RADOS_POOL_MAPS,
940 47462eda Filippos Giannakos
                     'blockpool': RADOS_POOL_BLOCKS,
941 47462eda Filippos Giannakos
                   }
942 47462eda Filippos Giannakos
else:
943 47462eda Filippos Giannakos
    BLOCK_PARAMS = { 'mappool': None,
944 47462eda Filippos Giannakos
                     'blockpool': None,
945 47462eda Filippos Giannakos
                   }
946 35d42381 Vangelis Koukis
947 761c2b3c root
948 edb7875c Christos Stavrakakis
_pithos_backend_pool = PithosBackendPool(size=POOL_SIZE,
949 edb7875c Christos Stavrakakis
                                         db_module=BACKEND_DB_MODULE,
950 edb7875c Christos Stavrakakis
                                         db_connection=BACKEND_DB_CONNECTION,
951 edb7875c Christos Stavrakakis
                                         block_module=BACKEND_BLOCK_MODULE,
952 edb7875c Christos Stavrakakis
                                         block_path=BACKEND_BLOCK_PATH,
953 edb7875c Christos Stavrakakis
                                         block_umask=BACKEND_BLOCK_UMASK,
954 edb7875c Christos Stavrakakis
                                         queue_module=BACKEND_QUEUE_MODULE,
955 b1dadd0e Sofia Papagiannaki
                                         queue_hosts=BACKEND_QUEUE_HOSTS,
956 b1dadd0e Sofia Papagiannaki
                                         queue_exchange=BACKEND_QUEUE_EXCHANGE,
957 3173699c Sofia Papagiannaki
                                         quotaholder_url=QUOTAHOLDER_URL,
958 3173699c Sofia Papagiannaki
                                         quotaholder_token=QUOTAHOLDER_TOKEN,
959 a9420179 Sofia Papagiannaki
                                         free_versioning=BACKEND_FREE_VERSIONING,
960 47462eda Filippos Giannakos
                                         block_params=BLOCK_PARAMS)
961 35d42381 Vangelis Koukis
962 35d42381 Vangelis Koukis
def get_backend():
963 edb7875c Christos Stavrakakis
    backend = _pithos_backend_pool.pool_get()
964 edb7875c Christos Stavrakakis
    backend.default_policy['quota'] = BACKEND_QUOTA
965 edb7875c Christos Stavrakakis
    backend.default_policy['versioning'] = BACKEND_VERSIONING
966 f77b1da9 Stratos Psomadakis
    backend.messages = []
967 edb7875c Christos Stavrakakis
    return backend
968 35d42381 Vangelis Koukis
969 35d42381 Vangelis Koukis
970 9fefc052 Antony Chazapis
def update_request_headers(request):
971 9fefc052 Antony Chazapis
    # Handle URL-encoded keys and values.
972 2715ade4 Sofia Papagiannaki
    meta = dict([(
973 2715ade4 Sofia Papagiannaki
        k, v) for k, v in request.META.iteritems() if k.startswith('HTTP_')])
974 88283e9e Antony Chazapis
    for k, v in meta.iteritems():
975 88283e9e Antony Chazapis
        try:
976 88283e9e Antony Chazapis
            k.decode('ascii')
977 88283e9e Antony Chazapis
            v.decode('ascii')
978 88283e9e Antony Chazapis
        except UnicodeDecodeError:
979 88283e9e Antony Chazapis
            raise BadRequest('Bad character in headers.')
980 88283e9e Antony Chazapis
        if '%' in k or '%' in v:
981 88283e9e Antony Chazapis
            del(request.META[k])
982 2715ade4 Sofia Papagiannaki
            request.META[unquote(k)] = smart_unicode(unquote(
983 2715ade4 Sofia Papagiannaki
                v), strings_only=True)
984 2715ade4 Sofia Papagiannaki
985 9fefc052 Antony Chazapis
986 b956618e Antony Chazapis
def update_response_headers(request, response):
987 b956618e Antony Chazapis
    if request.serialization == 'xml':
988 b956618e Antony Chazapis
        response['Content-Type'] = 'application/xml; charset=UTF-8'
989 b956618e Antony Chazapis
    elif request.serialization == 'json':
990 b956618e Antony Chazapis
        response['Content-Type'] = 'application/json; charset=UTF-8'
991 22dab079 Antony Chazapis
    elif not response['Content-Type']:
992 b956618e Antony Chazapis
        response['Content-Type'] = 'text/plain; charset=UTF-8'
993 2715ade4 Sofia Papagiannaki
994 9fefc052 Antony Chazapis
    if (not response.has_header('Content-Length') and
995 9fefc052 Antony Chazapis
        not (response.has_header('Content-Type') and
996 9fefc052 Antony Chazapis
             response['Content-Type'].startswith('multipart/byteranges'))):
997 b980169d Antony Chazapis
        response['Content-Length'] = len(response.content)
998 2715ade4 Sofia Papagiannaki
999 9fefc052 Antony Chazapis
    # URL-encode unicode in headers.
1000 9fefc052 Antony Chazapis
    meta = response.items()
1001 9fefc052 Antony Chazapis
    for k, v in meta:
1002 88283e9e Antony Chazapis
        if (k.startswith('X-Account-') or k.startswith('X-Container-') or
1003 2715ade4 Sofia Papagiannaki
                k.startswith('X-Object-') or k.startswith('Content-')):
1004 9fefc052 Antony Chazapis
            del(response[k])
1005 88283e9e Antony Chazapis
            response[quote(k)] = quote(v, safe='/=,:@; ')
1006 b956618e Antony Chazapis
1007 2715ade4 Sofia Papagiannaki
1008 b956618e Antony Chazapis
def render_fault(request, fault):
1009 a7dff008 Antony Chazapis
    if isinstance(fault, InternalServerError) and settings.DEBUG:
1010 b956618e Antony Chazapis
        fault.details = format_exc(fault)
1011 2715ade4 Sofia Papagiannaki
1012 b956618e Antony Chazapis
    request.serialization = 'text'
1013 08de868d Antony Chazapis
    data = fault.message + '\n'
1014 08de868d Antony Chazapis
    if fault.details:
1015 08de868d Antony Chazapis
        data += '\n' + fault.details
1016 b956618e Antony Chazapis
    response = HttpResponse(data, status=fault.code)
1017 b956618e Antony Chazapis
    update_response_headers(request, response)
1018 b956618e Antony Chazapis
    return response
1019 b956618e Antony Chazapis
1020 2715ade4 Sofia Papagiannaki
1021 b956618e Antony Chazapis
def request_serialization(request, format_allowed=False):
1022 58a6c894 Antony Chazapis
    """Return the serialization format requested.
1023 2715ade4 Sofia Papagiannaki

1024 b956618e Antony Chazapis
    Valid formats are 'text' and 'json', 'xml' if 'format_allowed' is True.
1025 b956618e Antony Chazapis
    """
1026 2715ade4 Sofia Papagiannaki
1027 b956618e Antony Chazapis
    if not format_allowed:
1028 b956618e Antony Chazapis
        return 'text'
1029 2715ade4 Sofia Papagiannaki
1030 b956618e Antony Chazapis
    format = request.GET.get('format')
1031 b956618e Antony Chazapis
    if format == 'json':
1032 b956618e Antony Chazapis
        return 'json'
1033 b956618e Antony Chazapis
    elif format == 'xml':
1034 b956618e Antony Chazapis
        return 'xml'
1035 2715ade4 Sofia Papagiannaki
1036 f9f15f92 Antony Chazapis
    for item in request.META.get('HTTP_ACCEPT', '').split(','):
1037 f9f15f92 Antony Chazapis
        accept, sep, rest = item.strip().partition(';')
1038 f9f15f92 Antony Chazapis
        if accept == 'application/json':
1039 f9f15f92 Antony Chazapis
            return 'json'
1040 f9f15f92 Antony Chazapis
        elif accept == 'application/xml' or accept == 'text/xml':
1041 f9f15f92 Antony Chazapis
            return 'xml'
1042 2715ade4 Sofia Papagiannaki
1043 b956618e Antony Chazapis
    return 'text'
1044 b956618e Antony Chazapis
1045 c846fad1 Sofia Papagiannaki
def get_pithos_usage(usage):
1046 c846fad1 Sofia Papagiannaki
    for u in usage:
1047 c846fad1 Sofia Papagiannaki
        if u.get('name') == 'pithos+.diskspace':
1048 c846fad1 Sofia Papagiannaki
            return u
1049 c846fad1 Sofia Papagiannaki
1050 c846fad1 Sofia Papagiannaki
def api_method(http_method=None, format_allowed=False, user_required=True,
1051 c846fad1 Sofia Papagiannaki
        request_usage=False):
1052 58a6c894 Antony Chazapis
    """Decorator function for views that implement an API method."""
1053 2715ade4 Sofia Papagiannaki
1054 b956618e Antony Chazapis
    def decorator(func):
1055 b956618e Antony Chazapis
        @wraps(func)
1056 b956618e Antony Chazapis
        def wrapper(request, *args, **kwargs):
1057 b956618e Antony Chazapis
            try:
1058 b956618e Antony Chazapis
                if http_method and request.method != http_method:
1059 b956618e Antony Chazapis
                    raise BadRequest('Method not allowed.')
1060 2715ade4 Sofia Papagiannaki
1061 dfdf4802 Sofia Papagiannaki
                if user_required:
1062 dfdf4802 Sofia Papagiannaki
                    token = None
1063 dfdf4802 Sofia Papagiannaki
                    if request.method in ('HEAD', 'GET') and COOKIE_NAME in request.COOKIES:
1064 2715ade4 Sofia Papagiannaki
                        cookie_value = unquote(
1065 2715ade4 Sofia Papagiannaki
                            request.COOKIES.get(COOKIE_NAME, ''))
1066 bf45cb4a Sofia Papagiannaki
                        account, sep, token = cookie_value.partition('|')
1067 2715ade4 Sofia Papagiannaki
                    get_user(request,
1068 c846fad1 Sofia Papagiannaki
                             AUTHENTICATION_URL,
1069 c846fad1 Sofia Papagiannaki
                             AUTHENTICATION_USERS,
1070 c846fad1 Sofia Papagiannaki
                             token,
1071 c846fad1 Sofia Papagiannaki
                             user_required)
1072 dfdf4802 Sofia Papagiannaki
                    if  getattr(request, 'user', None) is None:
1073 dfdf4802 Sofia Papagiannaki
                        raise Unauthorized('Access denied')
1074 b3102a96 Sofia Papagiannaki
                    assert getattr(request, 'user_uniq', None) != None
1075 c846fad1 Sofia Papagiannaki
                    request.user_usage = get_pithos_usage(
1076 2042a902 Sofia Papagiannaki
                        request.user.get('usage', []))
1077 2c22e4ac Antony Chazapis
                
1078 b956618e Antony Chazapis
                # The args variable may contain up to (account, container, object).
1079 b956618e Antony Chazapis
                if len(args) > 1 and len(args[1]) > 256:
1080 b956618e Antony Chazapis
                    raise BadRequest('Container name too large.')
1081 b956618e Antony Chazapis
                if len(args) > 2 and len(args[2]) > 1024:
1082 b956618e Antony Chazapis
                    raise BadRequest('Object name too large.')
1083 2715ade4 Sofia Papagiannaki
1084 9fefc052 Antony Chazapis
                # Format and check headers.
1085 9fefc052 Antony Chazapis
                update_request_headers(request)
1086 2715ade4 Sofia Papagiannaki
1087 b956618e Antony Chazapis
                # Fill in custom request variables.
1088 2715ade4 Sofia Papagiannaki
                request.serialization = request_serialization(
1089 2715ade4 Sofia Papagiannaki
                    request, format_allowed)
1090 228de81b Antony Chazapis
                request.backend = get_backend()
1091 2715ade4 Sofia Papagiannaki
1092 b956618e Antony Chazapis
                response = func(request, *args, **kwargs)
1093 b956618e Antony Chazapis
                update_response_headers(request, response)
1094 b956618e Antony Chazapis
                return response
1095 b956618e Antony Chazapis
            except Fault, fault:
1096 6e9c8fc1 Christos Stavrakakis
                if fault.code >= 500:
1097 6e9c8fc1 Christos Stavrakakis
                    logger.exception("API Fault")
1098 b956618e Antony Chazapis
                return render_fault(request, fault)
1099 b956618e Antony Chazapis
            except BaseException, e:
1100 b956618e Antony Chazapis
                logger.exception('Unexpected error: %s' % e)
1101 dfdf4802 Sofia Papagiannaki
                fault = InternalServerError('Unexpected error: %s' % e)
1102 b956618e Antony Chazapis
                return render_fault(request, fault)
1103 7b25e082 Antony Chazapis
            finally:
1104 297513ba Antony Chazapis
                if getattr(request, 'backend', None) is not None:
1105 d14fe290 Antony Chazapis
                    request.backend.close()
1106 b956618e Antony Chazapis
        return wrapper
1107 b956618e Antony Chazapis
    return decorator