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
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):
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."""
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."""
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):]
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':
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())
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()