From 56f3625bd90b6a3bf44559258307b8d2f5a5047c Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 18 Oct 2021 11:22:29 -0700 Subject: [PATCH] fix: support Last-Modified to parse full RFC7231 compliant format (#1575) fixes #1566 --- utils.go | 42 +++++++++++++++++++++++++++++++++++++----- utils_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/utils.go b/utils.go index 6c1c8c5124..ed388d7131 100644 --- a/utils.go +++ b/utils.go @@ -52,7 +52,7 @@ var expirationRegex = regexp.MustCompile(`expiry-date="(.*?)", rule-id="(.*?)"`) func amzExpirationToExpiryDateRuleID(expiration string) (time.Time, string) { if matches := expirationRegex.FindStringSubmatch(expiration); len(matches) == 3 { - expTime, err := time.Parse(http.TimeFormat, matches[1]) + expTime, err := parseRFC7231Time(matches[1]) if err != nil { return time.Time{}, "" } @@ -73,7 +73,7 @@ func amzRestoreToStruct(restore string) (ongoing bool, expTime time.Time, err er return false, time.Time{}, err } if matches[3] != "" { - expTime, err = time.Parse(http.TimeFormat, matches[3]) + expTime, err = parseRFC7231Time(matches[3]) if err != nil { return false, time.Time{}, err } @@ -240,6 +240,27 @@ func extractObjMetadata(header http.Header) http.Header { return filteredHeader } +const ( + // RFC 7231#section-7.1.1.1 timetamp format. e.g Tue, 29 Apr 2014 18:30:38 GMT + rfc822TimeFormat = "Mon, 2 Jan 2006 15:04:05 GMT" + rfc822TimeFormatSingleDigitDay = "Mon, _2 Jan 2006 15:04:05 GMT" + rfc822TimeFormatSingleDigitDayTwoDigitYear = "Mon, _2 Jan 06 15:04:05 GMT" +) + +func parseTime(t string, formats ...string) (time.Time, error) { + for _, format := range formats { + tt, err := time.Parse(format, t) + if err == nil { + return tt, nil + } + } + return time.Time{}, fmt.Errorf("unable to parse %s in any of the input formats: %s", t, formats) +} + +func parseRFC7231Time(lastModified string) (time.Time, error) { + return parseTime(lastModified, rfc822TimeFormat, rfc822TimeFormatSingleDigitDay, rfc822TimeFormatSingleDigitDayTwoDigitYear) +} + // ToObjectInfo converts http header values into ObjectInfo type, // extracts metadata and fills in all the necessary fields in ObjectInfo. func ToObjectInfo(bucketName string, objectName string, h http.Header) (ObjectInfo, error) { @@ -267,7 +288,7 @@ func ToObjectInfo(bucketName string, objectName string, h http.Header) (ObjectIn } // Parse Last-Modified has http time format. - date, err := time.Parse(http.TimeFormat, h.Get("Last-Modified")) + mtime, err := parseRFC7231Time(h.Get("Last-Modified")) if err != nil { return ObjectInfo{}, ErrorResponse{ Code: "InternalError", @@ -289,7 +310,18 @@ func ToObjectInfo(bucketName string, objectName string, h http.Header) (ObjectIn expiryStr := h.Get("Expires") var expiry time.Time if expiryStr != "" { - expiry, _ = time.Parse(http.TimeFormat, expiryStr) + expiry, err = parseRFC7231Time(expiryStr) + if err != nil { + return ObjectInfo{}, ErrorResponse{ + Code: "InternalError", + Message: fmt.Sprintf("'Expiry' is not in supported format: %v", err), + BucketName: bucketName, + Key: objectName, + RequestID: h.Get("x-amz-request-id"), + HostID: h.Get("x-amz-id-2"), + Region: h.Get("x-amz-bucket-region"), + } + } } metadata := extractObjMetadata(h) @@ -337,7 +369,7 @@ func ToObjectInfo(bucketName string, objectName string, h http.Header) (ObjectIn ETag: etag, Key: objectName, Size: size, - LastModified: date, + LastModified: mtime, ContentType: contentType, Expires: expiry, VersionID: h.Get(amzVersionID), diff --git a/utils_test.go b/utils_test.go index e494bbb857..9e4da53d31 100644 --- a/utils_test.go +++ b/utils_test.go @@ -26,6 +26,46 @@ import ( "github.com/minio/minio-go/v7/pkg/s3utils" ) +func TestParseRFC7231Time(t *testing.T) { + testCases := []struct { + timeStr string + expectedSuccess bool + }{ + { + timeStr: "Sun, 2 Jan 2000 20:34:56 GMT", + expectedSuccess: true, + }, + { + timeStr: "Sun, 02 Jan 2000 20:34:56 GMT", + expectedSuccess: true, + }, + { + timeStr: "Sun, 2 Jan 00 20:34:56 GMT", + expectedSuccess: true, + }, + { + timeStr: "Sun, 02 Jan 00 20:34:56 GMT", + expectedSuccess: true, + }, + { + timeStr: "Su, 2 Jan 00 20:34:56 GMT", + expectedSuccess: false, + }, + } + for _, testCase := range testCases { + testCase := testCase + t.Run("", func(t *testing.T) { + _, err := parseRFC7231Time(testCase.timeStr) + if err != nil && testCase.expectedSuccess { + t.Errorf("expected success found failure %v", err) + } + if err == nil && !testCase.expectedSuccess { + t.Errorf("expected failure found success") + } + }) + } +} + // Tests signature redacting function used // in filtering on-wire Authorization header. func TestRedactSignature(t *testing.T) {