The aws-sdk-java library was observed sending DELETE requests with a
Content-Type of "application/x-www-form-urlencoded; charset=utf-8".
If we allow that through, SLO will pass it along to the bulk-deleter,
which will respond with:
HTTP/1.1 200 OK
[various headers...]
Number Deleted: 0
Number Not Found: 0
Response Body:
Response Status: 406 Not Acceptable
Errors:
...and nothing will have been deleted. Worse, since the real error code
is buried in the body, it's difficult to detect the underlying cause.
Now, we'll just drop any content-type we receive from the client.
Another patch will be submitted to do something similar for SLO, but
this will fix multi-part deletes for older versions of Swift.
Related-Change: I791c7bda1d9df830d8dacd3783c2393db5a9ac09
Change-Id: I334726c61990fdbc984855d8ce562774c477cce9
143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
# Copyright (c) 2010-2014 OpenStack Foundation.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import sys
|
|
import datetime
|
|
|
|
from swift.common.http import HTTP_OK, HTTP_PARTIAL_CONTENT, HTTP_NO_CONTENT
|
|
from swift.common.swob import Range, content_range_header_value
|
|
|
|
from swift3.controllers.base import Controller
|
|
from swift3.response import S3NotImplemented, InvalidRange, NoSuchKey
|
|
|
|
|
|
class ObjectController(Controller):
|
|
"""
|
|
Handles requests on objects
|
|
"""
|
|
def _gen_head_range_resp(self, req_range, resp):
|
|
"""
|
|
Swift doesn't handle Range header for HEAD requests.
|
|
So, this mothod generates HEAD range response from HEAD response.
|
|
S3 return HEAD range response, if the value of range satisfies the
|
|
conditions which are described in the following document.
|
|
- http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
|
|
"""
|
|
length = long(resp.headers.get('Content-Length'))
|
|
|
|
try:
|
|
content_range = Range(req_range)
|
|
except ValueError:
|
|
return resp
|
|
|
|
ranges = content_range.ranges_for_length(length)
|
|
if ranges == []:
|
|
raise InvalidRange()
|
|
elif ranges:
|
|
if len(ranges) == 1:
|
|
start, end = ranges[0]
|
|
resp.headers['Content-Range'] = \
|
|
content_range_header_value(start, end, length)
|
|
resp.headers['Content-Length'] = (end - start)
|
|
resp.status = HTTP_PARTIAL_CONTENT
|
|
return resp
|
|
else:
|
|
# TODO: It is necessary to confirm whether need to respond to
|
|
# multi-part response.(e.g. bytes=0-10,20-30)
|
|
pass
|
|
|
|
return resp
|
|
|
|
def GETorHEAD(self, req):
|
|
resp = req.get_response(self.app)
|
|
|
|
if req.method == 'HEAD':
|
|
resp.app_iter = None
|
|
|
|
for key in ('content-type', 'content-language', 'expires',
|
|
'cache-control', 'content-disposition',
|
|
'content-encoding'):
|
|
if 'response-' + key in req.params:
|
|
resp.headers[key] = req.params['response-' + key]
|
|
|
|
return resp
|
|
|
|
def HEAD(self, req):
|
|
"""
|
|
Handle HEAD Object request
|
|
"""
|
|
resp = self.GETorHEAD(req)
|
|
|
|
if 'range' in req.headers:
|
|
req_range = req.headers['range']
|
|
resp = self._gen_head_range_resp(req_range, resp)
|
|
|
|
return resp
|
|
|
|
def GET(self, req):
|
|
"""
|
|
Handle GET Object request
|
|
"""
|
|
return self.GETorHEAD(req)
|
|
|
|
def PUT(self, req):
|
|
"""
|
|
Handle PUT Object and PUT Object (Copy) request
|
|
"""
|
|
req.check_copy_source(self.app)
|
|
resp = req.get_response(self.app)
|
|
|
|
if 'X-Amz-Copy-Source' in req.headers:
|
|
obj_timestamp = (datetime.datetime.fromtimestamp(
|
|
float(resp.environ['HTTP_X_TIMESTAMP']))
|
|
.isoformat())
|
|
if len(obj_timestamp) is 19:
|
|
obj_timestamp += '.000Z'
|
|
else:
|
|
obj_timestamp = obj_timestamp[:-3] + 'Z'
|
|
resp.append_copy_resp_body(req.controller_name,
|
|
obj_timestamp)
|
|
|
|
# delete object metadata from response
|
|
for key in list(resp.headers.keys()):
|
|
if key.startswith('x-amz-meta-'):
|
|
del resp.headers[key]
|
|
|
|
resp.status = HTTP_OK
|
|
return resp
|
|
|
|
def POST(self, req):
|
|
raise S3NotImplemented()
|
|
|
|
def DELETE(self, req):
|
|
"""
|
|
Handle DELETE Object request
|
|
"""
|
|
try:
|
|
query = req.gen_multipart_manifest_delete_query(self.app)
|
|
req.headers['Content-Type'] = None # Ignore client content-type
|
|
resp = req.get_response(self.app, query=query)
|
|
if query and resp.status_int == HTTP_OK:
|
|
for chunk in resp.app_iter:
|
|
pass # drain the bulk-deleter response
|
|
resp.status = HTTP_NO_CONTENT
|
|
resp.body = ''
|
|
except NoSuchKey:
|
|
# expect to raise NoSuchBucket when the bucket doesn't exist
|
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
req.get_container_info(self.app)
|
|
raise exc_type, exc_value, exc_traceback
|
|
return resp
|