Add normalize_etag() helper function
... and drive-by a import rename Co-Authored-By: Clay Gerrard <clay.gerrard@gmail.com> Change-Id: I1eaf075ff9855cfa03e7991bdf33375b0e4397e6
This commit is contained in:
parent
b3cd0cd4bb
commit
1f7b97ec0f
@ -29,11 +29,11 @@ from six.moves.http_client import HTTPException
|
|||||||
|
|
||||||
from swift.common.bufferedhttp import http_connect
|
from swift.common.bufferedhttp import http_connect
|
||||||
from swift.common.exceptions import ClientException
|
from swift.common.exceptions import ClientException
|
||||||
from swift.common.utils import Timestamp, FileLikeIter
|
from swift.common.swob import normalize_etag
|
||||||
|
from swift.common.utils import Timestamp, FileLikeIter, quote
|
||||||
from swift.common.http import HTTP_NO_CONTENT, HTTP_INSUFFICIENT_STORAGE, \
|
from swift.common.http import HTTP_NO_CONTENT, HTTP_INSUFFICIENT_STORAGE, \
|
||||||
is_success, is_server_error
|
is_success, is_server_error
|
||||||
from swift.common.header_key_dict import HeaderKeyDict
|
from swift.common.header_key_dict import HeaderKeyDict
|
||||||
from swift.common.utils import quote
|
|
||||||
|
|
||||||
|
|
||||||
class DirectClientException(ClientException):
|
class DirectClientException(ClientException):
|
||||||
@ -485,7 +485,7 @@ def direct_put_object(node, part, account, container, name, contents,
|
|||||||
if headers is None:
|
if headers is None:
|
||||||
headers = {}
|
headers = {}
|
||||||
if etag:
|
if etag:
|
||||||
headers['ETag'] = etag.strip('"')
|
headers['ETag'] = normalize_etag(etag)
|
||||||
if content_type is not None:
|
if content_type is not None:
|
||||||
headers['Content-Type'] = content_type
|
headers['Content-Type'] = content_type
|
||||||
else:
|
else:
|
||||||
@ -498,7 +498,7 @@ def direct_put_object(node, part, account, container, name, contents,
|
|||||||
'Object', conn_timeout, response_timeout, contents=contents,
|
'Object', conn_timeout, response_timeout, contents=contents,
|
||||||
content_length=content_length, chunk_size=chunk_size)
|
content_length=content_length, chunk_size=chunk_size)
|
||||||
|
|
||||||
return resp.getheader('etag').strip('"')
|
return normalize_etag(resp.getheader('etag'))
|
||||||
|
|
||||||
|
|
||||||
def direct_post_object(node, part, account, container, name, headers,
|
def direct_post_object(node, part, account, container, name, headers,
|
||||||
|
@ -25,7 +25,7 @@ from swift.common.request_helpers import get_object_transient_sysmeta, \
|
|||||||
strip_user_meta_prefix, is_user_meta, update_etag_is_at_header, \
|
strip_user_meta_prefix, is_user_meta, update_etag_is_at_header, \
|
||||||
get_container_update_override_key
|
get_container_update_override_key
|
||||||
from swift.common.swob import Request, Match, HTTPException, \
|
from swift.common.swob import Request, Match, HTTPException, \
|
||||||
HTTPUnprocessableEntity, wsgi_to_bytes, bytes_to_wsgi
|
HTTPUnprocessableEntity, wsgi_to_bytes, bytes_to_wsgi, normalize_etag
|
||||||
from swift.common.utils import get_logger, config_true_value, \
|
from swift.common.utils import get_logger, config_true_value, \
|
||||||
MD5_OF_EMPTY_STRING
|
MD5_OF_EMPTY_STRING
|
||||||
|
|
||||||
@ -263,7 +263,7 @@ class EncrypterObjContext(CryptoWSGIContext):
|
|||||||
ciphertext_etag = enc_input_proxy.ciphertext_md5.hexdigest()
|
ciphertext_etag = enc_input_proxy.ciphertext_md5.hexdigest()
|
||||||
mod_resp_headers = [
|
mod_resp_headers = [
|
||||||
(h, v if (h.lower() != 'etag' or
|
(h, v if (h.lower() != 'etag' or
|
||||||
v.strip('"') != ciphertext_etag)
|
normalize_etag(v) != ciphertext_etag)
|
||||||
else plaintext_etag)
|
else plaintext_etag)
|
||||||
for h, v in mod_resp_headers]
|
for h, v in mod_resp_headers]
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ from swift.common.exceptions import ListingIterError, SegmentError
|
|||||||
from swift.common.http import is_success
|
from swift.common.http import is_success
|
||||||
from swift.common.swob import Request, Response, \
|
from swift.common.swob import Request, Response, \
|
||||||
HTTPRequestedRangeNotSatisfiable, HTTPBadRequest, HTTPConflict, \
|
HTTPRequestedRangeNotSatisfiable, HTTPBadRequest, HTTPConflict, \
|
||||||
str_to_wsgi, wsgi_to_str, wsgi_quote, wsgi_unquote
|
str_to_wsgi, wsgi_to_str, wsgi_quote, wsgi_unquote, normalize_etag
|
||||||
from swift.common.utils import get_logger, \
|
from swift.common.utils import get_logger, \
|
||||||
RateLimitedIterator, quote, close_if_possible, closing_if_possible
|
RateLimitedIterator, quote, close_if_possible, closing_if_possible
|
||||||
from swift.common.request_helpers import SegmentedIterable
|
from swift.common.request_helpers import SegmentedIterable
|
||||||
@ -333,7 +333,7 @@ class GetContext(WSGIContext):
|
|||||||
if h.lower() != "etag"]
|
if h.lower() != "etag"]
|
||||||
etag = md5()
|
etag = md5()
|
||||||
for seg_dict in segments:
|
for seg_dict in segments:
|
||||||
etag.update(seg_dict['hash'].strip('"').encode('utf8'))
|
etag.update(normalize_etag(seg_dict['hash']).encode('utf8'))
|
||||||
response_headers.append(('Etag', '"%s"' % etag.hexdigest()))
|
response_headers.append(('Etag', '"%s"' % etag.hexdigest()))
|
||||||
|
|
||||||
app_iter = None
|
app_iter = None
|
||||||
|
@ -232,7 +232,7 @@ class BucketController(Controller):
|
|||||||
etag = o['s3_etag']
|
etag = o['s3_etag']
|
||||||
elif 'slo_etag' in o:
|
elif 'slo_etag' in o:
|
||||||
# SLOs may be in something *close* to the MU format
|
# SLOs may be in something *close* to the MU format
|
||||||
etag = '"%s-N"' % o['slo_etag'].strip('"')
|
etag = '"%s-N"' % swob.normalize_etag(o['slo_etag'])
|
||||||
else:
|
else:
|
||||||
etag = o['hash']
|
etag = o['hash']
|
||||||
if len(etag) < 2 or etag[::len(etag) - 1] != '""':
|
if len(etag) < 2 or etag[::len(etag) - 1] != '""':
|
||||||
|
@ -68,7 +68,7 @@ import time
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from swift.common.swob import Range, bytes_to_wsgi
|
from swift.common.swob import Range, bytes_to_wsgi, normalize_etag
|
||||||
from swift.common.utils import json, public, reiterate
|
from swift.common.utils import json, public, reiterate
|
||||||
from swift.common.db import utf8encode
|
from swift.common.db import utf8encode
|
||||||
from swift.common.request_helpers import get_container_update_override_key
|
from swift.common.request_helpers import get_container_update_override_key
|
||||||
@ -620,10 +620,7 @@ class UploadController(Controller):
|
|||||||
raise InvalidPartOrder(upload_id=upload_id)
|
raise InvalidPartOrder(upload_id=upload_id)
|
||||||
previous_number = part_number
|
previous_number = part_number
|
||||||
|
|
||||||
etag = part_elem.find('./ETag').text
|
etag = normalize_etag(part_elem.find('./ETag').text)
|
||||||
if len(etag) >= 2 and etag[0] == '"' and etag[-1] == '"':
|
|
||||||
# strip double quotes
|
|
||||||
etag = etag[1:-1]
|
|
||||||
if len(etag) != 32 or any(c not in '0123456789abcdef'
|
if len(etag) != 32 or any(c not in '0123456789abcdef'
|
||||||
for c in etag):
|
for c in etag):
|
||||||
raise InvalidPart(upload_id=upload_id,
|
raise InvalidPart(upload_id=upload_id,
|
||||||
|
@ -15,7 +15,8 @@
|
|||||||
|
|
||||||
from swift.common.http import HTTP_OK, HTTP_PARTIAL_CONTENT, HTTP_NO_CONTENT
|
from swift.common.http import HTTP_OK, HTTP_PARTIAL_CONTENT, HTTP_NO_CONTENT
|
||||||
from swift.common.request_helpers import update_etag_is_at_header
|
from swift.common.request_helpers import update_etag_is_at_header
|
||||||
from swift.common.swob import Range, content_range_header_value
|
from swift.common.swob import Range, content_range_header_value, \
|
||||||
|
normalize_etag
|
||||||
from swift.common.utils import public, list_from_csv
|
from swift.common.utils import public, list_from_csv
|
||||||
|
|
||||||
from swift.common.middleware.s3api.utils import S3Timestamp, sysmeta_header
|
from swift.common.middleware.s3api.utils import S3Timestamp, sysmeta_header
|
||||||
@ -68,8 +69,7 @@ class ObjectController(Controller):
|
|||||||
continue
|
continue
|
||||||
had_match = True
|
had_match = True
|
||||||
for value in list_from_csv(req.headers[match_header]):
|
for value in list_from_csv(req.headers[match_header]):
|
||||||
if value.startswith('"') and value.endswith('"'):
|
value = normalize_etag(value)
|
||||||
value = value[1:-1]
|
|
||||||
if value.endswith('-N'):
|
if value.endswith('-N'):
|
||||||
# Deal with fake S3-like etags for SLOs uploaded via Swift
|
# Deal with fake S3-like etags for SLOs uploaded via Swift
|
||||||
req.headers[match_header] += ', ' + value[:-2]
|
req.headers[match_header] += ', ' + value[:-2]
|
||||||
|
@ -331,7 +331,7 @@ from swift.common.swob import Request, HTTPBadRequest, HTTPServerError, \
|
|||||||
HTTPMethodNotAllowed, HTTPRequestEntityTooLarge, HTTPLengthRequired, \
|
HTTPMethodNotAllowed, HTTPRequestEntityTooLarge, HTTPLengthRequired, \
|
||||||
HTTPOk, HTTPPreconditionFailed, HTTPException, HTTPNotFound, \
|
HTTPOk, HTTPPreconditionFailed, HTTPException, HTTPNotFound, \
|
||||||
HTTPUnauthorized, HTTPConflict, HTTPUnprocessableEntity, \
|
HTTPUnauthorized, HTTPConflict, HTTPUnprocessableEntity, \
|
||||||
HTTPServiceUnavailable, Response, Range, \
|
HTTPServiceUnavailable, Response, Range, normalize_etag, \
|
||||||
RESPONSE_REASONS, str_to_wsgi, wsgi_to_str, wsgi_quote
|
RESPONSE_REASONS, str_to_wsgi, wsgi_to_str, wsgi_quote
|
||||||
from swift.common.utils import get_logger, config_true_value, \
|
from swift.common.utils import get_logger, config_true_value, \
|
||||||
get_valid_utf8_str, override_bytes_from_content_type, split_path, \
|
get_valid_utf8_str, override_bytes_from_content_type, split_path, \
|
||||||
@ -1324,8 +1324,8 @@ class StaticLargeObject(object):
|
|||||||
slo_etag.update(r.encode('ascii') if six.PY3 else r)
|
slo_etag.update(r.encode('ascii') if six.PY3 else r)
|
||||||
|
|
||||||
slo_etag = slo_etag.hexdigest()
|
slo_etag = slo_etag.hexdigest()
|
||||||
client_etag = req.headers.get('Etag')
|
client_etag = normalize_etag(req.headers.get('Etag'))
|
||||||
if client_etag and client_etag.strip('"') != slo_etag:
|
if client_etag and client_etag != slo_etag:
|
||||||
err = HTTPUnprocessableEntity(request=req)
|
err = HTTPUnprocessableEntity(request=req)
|
||||||
if heartbeat:
|
if heartbeat:
|
||||||
resp_dict = {}
|
resp_dict = {}
|
||||||
|
@ -689,6 +689,12 @@ class Range(object):
|
|||||||
return all_ranges
|
return all_ranges
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_etag(tag):
|
||||||
|
if tag and tag.startswith('"') and tag.endswith('"') and tag != '"':
|
||||||
|
return tag[1:-1]
|
||||||
|
return tag
|
||||||
|
|
||||||
|
|
||||||
class Match(object):
|
class Match(object):
|
||||||
"""
|
"""
|
||||||
Wraps a Request's If-[None-]Match header as a friendly object.
|
Wraps a Request's If-[None-]Match header as a friendly object.
|
||||||
@ -701,15 +707,10 @@ class Match(object):
|
|||||||
tag = tag.strip()
|
tag = tag.strip()
|
||||||
if not tag:
|
if not tag:
|
||||||
continue
|
continue
|
||||||
if tag.startswith('"') and tag.endswith('"'):
|
self.tags.add(normalize_etag(tag))
|
||||||
self.tags.add(tag[1:-1])
|
|
||||||
else:
|
|
||||||
self.tags.add(tag)
|
|
||||||
|
|
||||||
def __contains__(self, val):
|
def __contains__(self, val):
|
||||||
if val and val.startswith('"') and val.endswith('"'):
|
return '*' in self.tags or normalize_etag(val) in self.tags
|
||||||
val = val[1:-1]
|
|
||||||
return '*' in self.tags or val in self.tags
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '%s(%r)' % (
|
return '%s(%r)' % (
|
||||||
|
@ -36,6 +36,7 @@ from swift.common.internal_client import (
|
|||||||
from swift.common.exceptions import ClientException
|
from swift.common.exceptions import ClientException
|
||||||
from swift.common.ring import Ring
|
from swift.common.ring import Ring
|
||||||
from swift.common.ring.utils import is_local_device
|
from swift.common.ring.utils import is_local_device
|
||||||
|
from swift.common.swob import normalize_etag
|
||||||
from swift.common.utils import (
|
from swift.common.utils import (
|
||||||
clean_content_type, config_true_value,
|
clean_content_type, config_true_value,
|
||||||
FileLikeIter, get_logger, hash_path, quote, validate_sync_to,
|
FileLikeIter, get_logger, hash_path, quote, validate_sync_to,
|
||||||
@ -607,7 +608,7 @@ class ContainerSync(Daemon):
|
|||||||
if key in headers:
|
if key in headers:
|
||||||
del headers[key]
|
del headers[key]
|
||||||
if 'etag' in headers:
|
if 'etag' in headers:
|
||||||
headers['etag'] = headers['etag'].strip('"')
|
headers['etag'] = normalize_etag(headers['etag'])
|
||||||
if 'content-type' in headers:
|
if 'content-type' in headers:
|
||||||
headers['content-type'] = clean_content_type(
|
headers['content-type'] = clean_content_type(
|
||||||
headers['content-type'])
|
headers['content-type'])
|
||||||
|
@ -58,7 +58,7 @@ from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPCreated, \
|
|||||||
HTTPPreconditionFailed, HTTPRequestTimeout, HTTPUnprocessableEntity, \
|
HTTPPreconditionFailed, HTTPRequestTimeout, HTTPUnprocessableEntity, \
|
||||||
HTTPClientDisconnect, HTTPMethodNotAllowed, Request, Response, \
|
HTTPClientDisconnect, HTTPMethodNotAllowed, Request, Response, \
|
||||||
HTTPInsufficientStorage, HTTPForbidden, HTTPException, HTTPConflict, \
|
HTTPInsufficientStorage, HTTPForbidden, HTTPException, HTTPConflict, \
|
||||||
HTTPServerError, wsgi_to_bytes, wsgi_to_str
|
HTTPServerError, wsgi_to_bytes, wsgi_to_str, normalize_etag
|
||||||
from swift.obj.diskfile import RESERVED_DATAFILE_META, DiskFileRouter
|
from swift.obj.diskfile import RESERVED_DATAFILE_META, DiskFileRouter
|
||||||
from swift.obj.expirer import build_task_obj
|
from swift.obj.expirer import build_task_obj
|
||||||
|
|
||||||
@ -942,8 +942,8 @@ class ObjectController(BaseStorageServer):
|
|||||||
if (is_sys_or_user_meta('object', val[0]) or
|
if (is_sys_or_user_meta('object', val[0]) or
|
||||||
is_object_transient_sysmeta(val[0])))
|
is_object_transient_sysmeta(val[0])))
|
||||||
# N.B. footers_metadata is a HeaderKeyDict
|
# N.B. footers_metadata is a HeaderKeyDict
|
||||||
received_etag = footers_metadata.get('etag', request.headers.get(
|
received_etag = normalize_etag(footers_metadata.get(
|
||||||
'etag', '')).strip('"')
|
'etag', request.headers.get('etag', '')))
|
||||||
if received_etag and received_etag != metadata['ETag']:
|
if received_etag and received_etag != metadata['ETag']:
|
||||||
raise HTTPUnprocessableEntity(request=request)
|
raise HTTPUnprocessableEntity(request=request)
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ from swift.common.http import is_informational, is_success, is_redirection, \
|
|||||||
HTTP_INSUFFICIENT_STORAGE, HTTP_UNAUTHORIZED, HTTP_CONTINUE, HTTP_GONE
|
HTTP_INSUFFICIENT_STORAGE, HTTP_UNAUTHORIZED, HTTP_CONTINUE, HTTP_GONE
|
||||||
from swift.common.swob import Request, Response, Range, \
|
from swift.common.swob import Request, Response, Range, \
|
||||||
HTTPException, HTTPRequestedRangeNotSatisfiable, HTTPServiceUnavailable, \
|
HTTPException, HTTPRequestedRangeNotSatisfiable, HTTPServiceUnavailable, \
|
||||||
status_map, wsgi_to_str, str_to_wsgi, wsgi_quote
|
status_map, wsgi_to_str, str_to_wsgi, wsgi_quote, normalize_etag
|
||||||
from swift.common.request_helpers import strip_sys_meta_prefix, \
|
from swift.common.request_helpers import strip_sys_meta_prefix, \
|
||||||
strip_user_meta_prefix, is_user_meta, is_sys_meta, is_sys_or_user_meta, \
|
strip_user_meta_prefix, is_user_meta, is_sys_meta, is_sys_or_user_meta, \
|
||||||
http_response_to_document_iters, is_object_transient_sysmeta, \
|
http_response_to_document_iters, is_object_transient_sysmeta, \
|
||||||
@ -1268,9 +1268,9 @@ class ResumingGetter(object):
|
|||||||
close_swift_conn(possible_source)
|
close_swift_conn(possible_source)
|
||||||
else:
|
else:
|
||||||
if self.used_source_etag and \
|
if self.used_source_etag and \
|
||||||
self.used_source_etag != src_headers.get(
|
self.used_source_etag != normalize_etag(src_headers.get(
|
||||||
'x-object-sysmeta-ec-etag',
|
'x-object-sysmeta-ec-etag',
|
||||||
src_headers.get('etag', '')).strip('"'):
|
src_headers.get('etag', ''))):
|
||||||
self.statuses.append(HTTP_NOT_FOUND)
|
self.statuses.append(HTTP_NOT_FOUND)
|
||||||
self.reasons.append('')
|
self.reasons.append('')
|
||||||
self.bodies.append('')
|
self.bodies.append('')
|
||||||
@ -1373,9 +1373,8 @@ class ResumingGetter(object):
|
|||||||
# from the same object (EC). Otherwise, if the cluster has two
|
# from the same object (EC). Otherwise, if the cluster has two
|
||||||
# versions of the same object, we might end up switching between
|
# versions of the same object, we might end up switching between
|
||||||
# old and new mid-stream and giving garbage to the client.
|
# old and new mid-stream and giving garbage to the client.
|
||||||
self.used_source_etag = src_headers.get(
|
self.used_source_etag = normalize_etag(src_headers.get(
|
||||||
'x-object-sysmeta-ec-etag',
|
'x-object-sysmeta-ec-etag', src_headers.get('etag', '')))
|
||||||
src_headers.get('etag', '')).strip('"')
|
|
||||||
self.node = node
|
self.node = node
|
||||||
return source, node
|
return source, node
|
||||||
return None, None
|
return None, None
|
||||||
@ -1922,7 +1921,7 @@ class Controller(object):
|
|||||||
if headers:
|
if headers:
|
||||||
update_headers(resp, headers[status_index])
|
update_headers(resp, headers[status_index])
|
||||||
if etag:
|
if etag:
|
||||||
resp.headers['etag'] = etag.strip('"')
|
resp.headers['etag'] = normalize_etag(etag)
|
||||||
return resp
|
return resp
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -70,7 +70,8 @@ from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPNotFound, \
|
|||||||
HTTPPreconditionFailed, HTTPRequestEntityTooLarge, HTTPRequestTimeout, \
|
HTTPPreconditionFailed, HTTPRequestEntityTooLarge, HTTPRequestTimeout, \
|
||||||
HTTPServerError, HTTPServiceUnavailable, HTTPClientDisconnect, \
|
HTTPServerError, HTTPServiceUnavailable, HTTPClientDisconnect, \
|
||||||
HTTPUnprocessableEntity, Response, HTTPException, \
|
HTTPUnprocessableEntity, Response, HTTPException, \
|
||||||
HTTPRequestedRangeNotSatisfiable, Range, HTTPInternalServerError
|
HTTPRequestedRangeNotSatisfiable, Range, HTTPInternalServerError, \
|
||||||
|
normalize_etag
|
||||||
from swift.common.request_helpers import update_etag_is_at_header, \
|
from swift.common.request_helpers import update_etag_is_at_header, \
|
||||||
resolve_etag_is_at_header, validate_internal_obj
|
resolve_etag_is_at_header, validate_internal_obj
|
||||||
|
|
||||||
@ -478,7 +479,7 @@ class BaseObjectController(Controller):
|
|||||||
{'status': response.status,
|
{'status': response.status,
|
||||||
'body': body[:1024], 'path': req.path})
|
'body': body[:1024], 'path': req.path})
|
||||||
elif is_success(response.status):
|
elif is_success(response.status):
|
||||||
etags.add(response.getheader('etag').strip('"'))
|
etags.add(normalize_etag(response.getheader('etag')))
|
||||||
|
|
||||||
for (putter, response) in pile:
|
for (putter, response) in pile:
|
||||||
if response:
|
if response:
|
||||||
@ -2638,8 +2639,8 @@ class ECObjectController(BaseObjectController):
|
|||||||
computed_etag = (etag_hasher.hexdigest()
|
computed_etag = (etag_hasher.hexdigest()
|
||||||
if etag_hasher else None)
|
if etag_hasher else None)
|
||||||
footers = self._get_footers(req)
|
footers = self._get_footers(req)
|
||||||
received_etag = footers.get('etag', req.headers.get(
|
received_etag = normalize_etag(footers.get(
|
||||||
'etag', '')).strip('"')
|
'etag', req.headers.get('etag', '')))
|
||||||
if (computed_etag and received_etag and
|
if (computed_etag and received_etag and
|
||||||
computed_etag != received_etag):
|
computed_etag != received_etag):
|
||||||
raise HTTPUnprocessableEntity(request=req)
|
raise HTTPUnprocessableEntity(request=req)
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user