X-Git-Url: https://code.grnet.gr/git/pithos/blobdiff_plain/817890f298fd2c6605b07eefcdcc3ee925e09267..7301127c9709b99c41ca8d6a3702f3fd42f65a07:/pithos/api/util.py diff --git a/pithos/api/util.py b/pithos/api/util.py index ccc83ec..1ac568f 100644 --- a/pithos/api/util.py +++ b/pithos/api/util.py @@ -37,19 +37,23 @@ from traceback import format_exc from wsgiref.handlers import format_date_time from binascii import hexlify, unhexlify from datetime import datetime, tzinfo, timedelta +from urllib import quote, unquote 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) +from pithos.api.short_url import encode_url from pithos.backends import connect_backend from pithos.backends.base import NotAllowedError, QuotaError @@ -96,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): @@ -194,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.""" @@ -241,7 +246,7 @@ def update_sharing_meta(request, permissions, v_account, v_container, v_object, def update_public_meta(public, meta): if not public: return - meta['X-Object-Public'] = public + meta['X-Object-Public'] = '/public/' + encode_url(public) def validate_modification_preconditions(request, meta): """Check that the modified timestamp conforms with the preconditions set.""" @@ -288,7 +293,7 @@ def split_container_object_string(s): raise ValueError s = s[1:] pos = s.find('/') - if pos == -1: + if pos == -1 or pos == len(s) - 1: raise ValueError return s[:pos], s[(pos + 1):] @@ -738,24 +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. + # 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': @@ -765,9 +775,19 @@ def update_response_headers(request, response): elif not response['Content-Type']: response['Content-Type'] = 'text/plain; charset=UTF-8' - if not response.has_header('Content-Length') and not (response.has_header('Content-Type') and response['Content-Type'].startswith('multipart/byteranges')): + if (not response.has_header('Content-Length') and + not (response.has_header('Content-Type') and + response['Content-Type'].startswith('multipart/byteranges'))): response['Content-Length'] = len(response.content) + # URL-encode unicode in headers. + meta = response.items() + for k, v in meta: + 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='/=,:@; ') + if settings.TEST: response['Date'] = format_date_time(time()) @@ -823,6 +843,9 @@ def api_method(http_method=None, format_allowed=False, user_required=True): if len(args) > 2 and len(args[2]) > 1024: raise BadRequest('Object name too large.') + # Format and check headers. + update_request_headers(request) + # Fill in custom request variables. request.serialization = request_serialization(request, format_allowed) request.backend = connect_backend()