diff --git a/fstest/mockobject/mockobject.go b/fstest/mockobject/mockobject.go index 084536b85..dad56a634 100644 --- a/fstest/mockobject/mockobject.go +++ b/fstest/mockobject/mockobject.go @@ -93,13 +93,14 @@ const ( // SeekModes contains all valid SeekMode's var SeekModes = []SeekMode{SeekModeNone, SeekModeRegular, SeekModeRange} -// ContentMockObject mocks an fs.Object and has content +// ContentMockObject mocks an fs.Object and has content, mod time type ContentMockObject struct { Object content []byte seekMode SeekMode f fs.Fs unknownSize bool + modTime time.Time } // WithContent returns an fs.Object with the given content. @@ -192,6 +193,18 @@ func (o *ContentMockObject) Hash(ctx context.Context, t hash.Type) (string, erro return hasher.Sums()[t], nil } +// ModTime returns the modification date of the file +// It should return a best guess if one isn't available +func (o *ContentMockObject) ModTime(ctx context.Context) time.Time { + return o.modTime +} + +// SetModTime sets the metadata on the object to set the modification date +func (o *ContentMockObject) SetModTime(ctx context.Context, t time.Time) error { + o.modTime = t + return nil +} + type readCloser struct{ io.Reader } func (r *readCloser) Close() error { return nil } diff --git a/lib/http/serve/serve.go b/lib/http/serve/serve.go index c4b70f1fd..6f97d0d8a 100644 --- a/lib/http/serve/serve.go +++ b/lib/http/serve/serve.go @@ -35,6 +35,10 @@ func Object(w http.ResponseWriter, r *http.Request, o fs.Object) { w.Header().Set("Content-Type", mimeType) } + // Set last modified + modTime := o.ModTime(r.Context()) + w.Header().Set("Last-Modified", modTime.UTC().Format(http.TimeFormat)) + if r.Method == "HEAD" { return } diff --git a/lib/http/serve/serve_test.go b/lib/http/serve/serve_test.go index b6cc70976..dc9098428 100644 --- a/lib/http/serve/serve_test.go +++ b/lib/http/serve/serve_test.go @@ -1,10 +1,12 @@ package serve import ( + "context" "io" "net/http" "net/http/httptest" "testing" + "time" "github.com/rclone/rclone/fstest/mockobject" "github.com/stretchr/testify/assert" @@ -25,11 +27,13 @@ func TestObjectHEAD(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest("HEAD", "http://example.com/aFile", nil) o := mockobject.New("aFile").WithContent([]byte("hello"), mockobject.SeekModeNone) + _ = o.SetModTime(context.Background(), time.Date(2023, 9, 20, 12, 11, 15, 0, time.FixedZone("", 4*60*60))) // UTC+4 Object(w, r, o) resp := w.Result() assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, "5", resp.Header.Get("Content-Length")) assert.Equal(t, "bytes", resp.Header.Get("Accept-Ranges")) + assert.Equal(t, "Wed, 20 Sep 2023 08:11:15 GMT", resp.Header.Get("Last-Modified")) body, _ := io.ReadAll(resp.Body) assert.Equal(t, "", string(body)) } @@ -38,11 +42,13 @@ func TestObjectGET(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest("GET", "http://example.com/aFile", nil) o := mockobject.New("aFile").WithContent([]byte("hello"), mockobject.SeekModeNone) + _ = o.SetModTime(context.Background(), time.Date(2023, 9, 20, 12, 11, 15, 0, time.FixedZone("", 2*60*60))) // UTC+2 Object(w, r, o) resp := w.Result() assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, "5", resp.Header.Get("Content-Length")) assert.Equal(t, "bytes", resp.Header.Get("Accept-Ranges")) + assert.Equal(t, "Wed, 20 Sep 2023 10:11:15 GMT", resp.Header.Get("Last-Modified")) body, _ := io.ReadAll(resp.Body) assert.Equal(t, "hello", string(body)) }