Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-app / pithos / api / util.py @ ccfd4e44

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

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

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

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

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

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

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

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