Merge branch 'meta-domains'
[pithos] / pithos / api / util.py
index c2d835c..5ac6a72 100644 (file)
@@ -43,11 +43,13 @@ from django.conf import settings
 from django.http import HttpResponse
 from django.utils import simplejson as json
 from django.utils.http import http_date, parse_etags
-from django.utils.encoding import smart_str
+from django.utils.encoding import smart_unicode, smart_str
 from django.core.files.uploadhandler import FileUploadHandler
 from django.core.files.uploadedfile import UploadedFile
 
-from pithos.api.compat import parse_http_date_safe, parse_http_date
+from pithos.lib.compat import parse_http_date_safe, parse_http_date
+from pithos.lib.hashmap import HashMap
+
 from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound,
                                 Conflict, LengthRequired, PreconditionFailed, RequestEntityTooLarge,
                                 RangeNotSatisfiable, ServiceUnavailable)
@@ -98,7 +100,8 @@ def printable_header_dict(d):
     Format 'last_modified' timestamp.
     """
     
-    d['last_modified'] = isoformat(datetime.fromtimestamp(d['last_modified']))
+    if 'last_modified' in d:
+        d['last_modified'] = isoformat(datetime.fromtimestamp(d['last_modified']))
     return dict([(k.lower().replace('-', '_'), v) for k, v in d.iteritems()])
 
 def format_header_key(k):
@@ -177,7 +180,7 @@ def get_object_headers(request):
     return meta, get_sharing(request), get_public(request)
 
 def put_object_headers(response, meta, restricted=False):
-    response['ETag'] = meta['ETag']
+    response['ETag'] = meta['ETag'] if 'ETag' in meta else meta['hash']
     response['Content-Length'] = meta['bytes']
     response['Content-Type'] = meta.get('Content-Type', 'application/octet-stream')
     response['Last-Modified'] = http_date(int(meta['modified']))
@@ -196,7 +199,7 @@ def put_object_headers(response, meta, restricted=False):
     else:
         for k in ('Content-Encoding', 'Content-Disposition'):
             if k in meta:
-                response[k] = meta[k]
+                response[k] = smart_str(meta[k], strings_only=True)
 
 def update_manifest_meta(request, v_account, meta):
     """Update metadata if the object has an X-Object-Manifest."""
@@ -303,11 +306,11 @@ def copy_or_move_object(request, src_account, src_container, src_name, dest_acco
         if move:
             version_id = request.backend.move_object(request.user_uniq, src_account, src_container, src_name,
                                                         dest_account, dest_container, dest_name,
-                                                        meta, False, permissions)
+                                                        'pithos', meta, False, permissions)
         else:
             version_id = request.backend.copy_object(request.user_uniq, src_account, src_container, src_name,
                                                         dest_account, dest_container, dest_name,
-                                                        meta, False, permissions, src_version)
+                                                        'pithos', meta, False, permissions, src_version)
     except NotAllowedError:
         raise Forbidden('Not allowed')
     except (NameError, IndexError):
@@ -740,38 +743,29 @@ def put_object_block(request, hashmap, data, offset):
 def hashmap_hash(request, hashmap):
     """Produce the root hash, treating the hashmap as a Merkle-like tree."""
     
-    def subhash(d):
-        h = hashlib.new(request.backend.hash_algorithm)
-        h.update(d)
-        return h.digest()
-    
-    if len(hashmap) == 0:
-        return hexlify(subhash(''))
-    if len(hashmap) == 1:
-        return hashmap[0]
-    
-    s = 2
-    while s < len(hashmap):
-        s = s * 2
-    h = [unhexlify(x) for x in hashmap]
-    h += [('\x00' * len(h[0]))] * (s - len(hashmap))
-    while len(h) > 1:
-        h = [subhash(h[x] + h[x + 1]) for x in range(0, len(h), 2)]
-    return hexlify(h[0])
+    map = HashMap(request.backend.block_size, request.backend.hash_algorithm)
+    map.extend([unhexlify(x) for x in hashmap])
+    return hexlify(map.hash())
 
 def update_request_headers(request):
     # Handle URL-encoded keys and values.
-    meta = request.META
-    for k, v in meta.copy().iteritems():
-        if ((k.startswith('HTTP_X_ACCOUNT_META_') or k.startswith('HTTP_X_ACCOUNT_GROUP_') or
-             k.startswith('HTTP_X_CONTAINER_META_') or k.startswith('HTTP_X_OBJECT_META_') or
-             k in ('HTTP_X_OBJECT_MANIFEST', 'HTTP_X_OBJECT_SHARING',
-                   'HTTP_X_COPY_FROM', 'HTTP_X_MOVE_FROM',
-                   'HTTP_X_SOURCE_ACCOUNT', 'HTTP_X_SOURCE_OBJECT',
-                   'HTTP_DESTINATION_ACCOUNT', 'HTTP_DESTINATION')) and
-            ('%' in k or '%' in v)):
-            del(meta[k])
-            meta[unquote(k)] = unquote(v)
+    # Handle URL-encoded keys and values.
+    meta = dict([(k, v) for k, v in request.META.iteritems() if k.startswith('HTTP_')])
+    if len(meta) > 90:
+        raise BadRequest('Too many headers.')
+    for k, v in meta.iteritems():
+        if len(k) > 128:
+            raise BadRequest('Header name too large.')
+        if len(v) > 256:
+            raise BadRequest('Header value too large.')
+        try:
+            k.decode('ascii')
+            v.decode('ascii')
+        except UnicodeDecodeError:
+            raise BadRequest('Bad character in headers.')
+        if '%' in k or '%' in v:
+            del(request.META[k])
+            request.META[unquote(k)] = smart_unicode(unquote(v), strings_only=True)
 
 def update_response_headers(request, response):
     if request.serialization == 'xml':
@@ -789,11 +783,10 @@ def update_response_headers(request, response):
     # URL-encode unicode in headers.
     meta = response.items()
     for k, v in meta:
-        if (k.startswith('X-Account-Meta-') or k.startswith('X-Account-Group-') or
-            k.startswith('X-Container-Meta-') or k.startswith('X-Object-Meta-') or
-            k in ('X-Container-Object-Meta', 'X-Object-Manifest', 'X-Object-Sharing', 'X-Object-Shared-By')):
+        if (k.startswith('X-Account-') or k.startswith('X-Container-') or
+            k.startswith('X-Object-') or k.startswith('Content-')):
             del(response[k])
-            response[quote(k)] = quote(v, safe='/=,:@')
+            response[quote(k)] = quote(v, safe='/=,:@; ')
     
     if settings.TEST:
         response['Date'] = format_date_time(time())
@@ -852,14 +845,6 @@ def api_method(http_method=None, format_allowed=False, user_required=True):
                 
                 # Format and check headers.
                 update_request_headers(request)
-                meta = dict([(k, v) for k, v in request.META.iteritems() if k.startswith('HTTP_')])
-                if len(meta) > 90:
-                    raise BadRequest('Too many headers.')
-                for k, v in meta.iteritems():
-                    if len(k) > 128:
-                        raise BadRequest('Header name too large.')
-                    if len(v) > 256:
-                        raise BadRequest('Header value too large.')
                 
                 # Fill in custom request variables.
                 request.serialization = request_serialization(request, format_allowed)