From 517416c4d95eb31d40ac3fa81921ac6873f3a72b Mon Sep 17 00:00:00 2001 From: Simon-Pierre Gingras Date: Fri, 19 May 2017 15:59:25 -0700 Subject: [PATCH] feat(s3) HeadObject: honor If-Modified-Since header --- moto/core/utils.py | 8 +++++++- moto/s3/responses.py | 12 +++++++++++- tests/test_s3/test_s3.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/moto/core/utils.py b/moto/core/utils.py index 7d4a9d412..9ee0c1814 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -174,11 +174,17 @@ def iso_8601_datetime_without_milliseconds(datetime): return datetime.strftime("%Y-%m-%dT%H:%M:%S") + 'Z' +RFC1123 = '%a, %d %b %Y %H:%M:%S GMT' + + def rfc_1123_datetime(datetime): - RFC1123 = '%a, %d %b %Y %H:%M:%S GMT' return datetime.strftime(RFC1123) +def str_to_rfc_1123_datetime(str): + return datetime.datetime.strptime(str, RFC1123) + + def unix_time(dt=None): dt = dt or datetime.datetime.utcnow() epoch = datetime.datetime.utcfromtimestamp(0) diff --git a/moto/s3/responses.py b/moto/s3/responses.py index fd33c5ead..43e27a815 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import re import six +from moto.core.utils import str_to_rfc_1123_datetime from six.moves.urllib.parse import parse_qs, urlparse import xmltodict @@ -595,12 +596,21 @@ class ResponseObject(_TemplateEnvironmentMixin): def _key_response_head(self, bucket_name, query, key_name, headers): response_headers = {} version_id = query.get('versionId', [None])[0] + + if_modified_since = headers.get('if-modified-since', None) + if if_modified_since: + if_modified_since = str_to_rfc_1123_datetime(if_modified_since) + key = self.backend.get_key( bucket_name, key_name, version_id=version_id) if key: response_headers.update(key.metadata) response_headers.update(key.response_dict) - return 200, response_headers, "" + + if if_modified_since and key.last_modified < if_modified_since: + return 304, response_headers, 'Not Modified' + else: + return 200, response_headers, "" else: return 404, response_headers, "" diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index de9c6a7de..6af653f9e 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals + +import datetime from six.moves.urllib.request import urlopen from six.moves.urllib.error import HTTPError from functools import wraps @@ -10,6 +12,7 @@ import json import boto import boto3 from botocore.client import ClientError +import botocore.exceptions from boto.exception import S3CreateError, S3ResponseError from boto.s3.connection import S3Connection from boto.s3.key import Key @@ -1266,6 +1269,31 @@ def test_boto3_head_object_with_versioning(): old_head_object['ContentLength'].should.equal(len(old_content)) +@mock_s3 +def test_boto3_head_object_if_modified_since(): + s3 = boto3.client('s3', region_name='us-east-1') + bucket_name = "blah" + s3.create_bucket(Bucket=bucket_name) + + key = 'hello.txt' + + with freeze_time(datetime.datetime.now() - datetime.timedelta(hours=3)): + s3.put_object( + Bucket=bucket_name, + Key=key, + Body='test' + ) + + with assert_raises(botocore.exceptions.ClientError) as err: + s3.head_object( + Bucket=bucket_name, + Key=key, + IfModifiedSince=datetime.datetime.now() - datetime.timedelta(hours=2) + ) + e = err.exception + e.response['Error'].should.equal({'Code': '304', 'Message': 'Not Modified'}) + + @mock_s3 @reduced_min_part_size def test_boto3_multipart_etag():