From 546e0d0a118431c1aed221bed822f2ba03c69bbb Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 9 Apr 2021 14:27:22 +0000 Subject: [PATCH 01/11] Add ETag header. --- routers/repo/attachment.go | 22 +++++++++++----------- routers/repo/download.go | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/routers/repo/attachment.go b/routers/repo/attachment.go index a896e4a50157..bf172cdf0f8d 100644 --- a/routers/repo/attachment.go +++ b/routers/repo/attachment.go @@ -124,21 +124,26 @@ func GetAttachment(ctx *context.Context) { } } + if err := attach.IncreaseDownloadCount(); err != nil { + ctx.ServerError("Update", err) + return + } + if setting.Attachment.ServeDirect { //If we have a signed url (S3, object storage), redirect to this directly. u, err := storage.Attachments.URL(attach.RelativePath(), attach.Name) if u != nil && err == nil { - if err := attach.IncreaseDownloadCount(); err != nil { - ctx.ServerError("Update", err) - return - } - ctx.Redirect(u.String()) return } } + etag := `"` + attach.UUID + `"` + if handleETag(ctx, etag) { + return + } + //If we have matched and access to release or issue fr, err := storage.Attachments.Open(attach.RelativePath()) if err != nil { @@ -147,12 +152,7 @@ func GetAttachment(ctx *context.Context) { } defer fr.Close() - if err := attach.IncreaseDownloadCount(); err != nil { - ctx.ServerError("Update", err) - return - } - - if err = ServeData(ctx, attach.Name, attach.Size, fr); err != nil { + if err = ServeData(ctx, etag, attach.Name, attach.Size, fr); err != nil { ctx.ServerError("ServeData", err) return } diff --git a/routers/repo/download.go b/routers/repo/download.go index 63a9ca47d7c6..432518ee88ee 100644 --- a/routers/repo/download.go +++ b/routers/repo/download.go @@ -8,6 +8,7 @@ package repo import ( "fmt" "io" + "net/http" "path" "strings" @@ -20,7 +21,7 @@ import ( ) // ServeData download file from io.Reader -func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) error { +func ServeData(ctx *context.Context, etag, name string, size int64, reader io.Reader) error { buf := make([]byte, 1024) n, err := reader.Read(buf) if err != nil && err != io.EOF { @@ -31,6 +32,9 @@ func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) } ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400") + if len(etag) > 0 { + ctx.Resp.Header().Set("Etag", etag) + } if size >= 0 { ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size)) } else { @@ -71,6 +75,11 @@ func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) // ServeBlob download a git.Blob func ServeBlob(ctx *context.Context, blob *git.Blob) error { + etag := `"` + blob.ID.String() + `"` + if handleETag(ctx, etag) { + return nil + } + dataRc, err := blob.DataAsync() if err != nil { return err @@ -81,11 +90,15 @@ func ServeBlob(ctx *context.Context, blob *git.Blob) error { } }() - return ServeData(ctx, ctx.Repo.TreePath, blob.Size(), dataRc) + return ServeData(ctx, etag, ctx.Repo.TreePath, blob.Size(), dataRc) } // ServeBlobOrLFS download a git.Blob redirecting to LFS if necessary func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error { + if handleETag(ctx, `"`+blob.ID.String()+`"`) { + return nil + } + dataRc, err := blob.DataAsync() if err != nil { return err @@ -102,6 +115,10 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error { if meta == nil { return ServeBlob(ctx, blob) } + etag := `"` + pointer.Oid + `"` + if handleETag(ctx, etag) { + return nil + } lfsDataRc, err := lfs.ReadMetaObject(meta.Pointer) if err != nil { return err @@ -111,12 +128,22 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error { log.Error("ServeBlobOrLFS: Close: %v", err) } }() - return ServeData(ctx, ctx.Repo.TreePath, meta.Size, lfsDataRc) + return ServeData(ctx, etag, ctx.Repo.TreePath, meta.Size, lfsDataRc) } return ServeBlob(ctx, blob) } +// handleETag returns true if the etag matches the request and sends StatusNotModified +func handleETag(ctx *context.Context, etag string) bool { + if ctx.Req.Header.Get("If-None-Match") == etag { + ctx.Resp.Header().Set("Etag", etag) + ctx.Resp.WriteHeader(http.StatusNotModified) + return true + } + return false +} + // SingleDownload download a file by repos path func SingleDownload(ctx *context.Context) { blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreePath) From e1ab0524694b519a56587c5928780b0815f730ad Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 9 Apr 2021 14:27:49 +0000 Subject: [PATCH 02/11] Comply with RFC 7232. --- modules/httpcache/httpcache.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index cf35cef129b4..1a4ad5e4eaf2 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -26,7 +26,7 @@ func GetCacheControl() string { // generateETag generates an ETag based on size, filename and file modification time func generateETag(fi os.FileInfo) string { etag := fmt.Sprint(fi.Size()) + fi.Name() + fi.ModTime().UTC().Format(http.TimeFormat) - return base64.StdEncoding.EncodeToString([]byte(etag)) + return `"` + base64.StdEncoding.EncodeToString([]byte(etag)) + `"` } // HandleTimeCache handles time-based caching for a HTTP request @@ -49,6 +49,7 @@ func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) ( func HandleEtagCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) { etag := generateETag(fi) if req.Header.Get("If-None-Match") == etag { + w.Header().Set("ETag", etag) w.WriteHeader(http.StatusNotModified) return true } From dd6872238b32f260b6a227063ddb0de3e8d970d4 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 9 Apr 2021 18:01:00 +0000 Subject: [PATCH 03/11] Moved logic into httpcache.go --- modules/httpcache/httpcache.go | 28 ++++++++++++++++++---------- modules/public/public.go | 2 +- routers/repo/attachment.go | 6 +++--- routers/repo/download.go | 30 ++++++++---------------------- 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index 1a4ad5e4eaf2..bbd57e04c4f6 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -31,6 +31,8 @@ func generateETag(fi os.FileInfo) string { // HandleTimeCache handles time-based caching for a HTTP request func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) { + w.Header().Set("Cache-Control", GetCacheControl()) + ifModifiedSince := req.Header.Get("If-Modified-Since") if ifModifiedSince != "" { t, err := time.Parse(http.TimeFormat, ifModifiedSince) @@ -40,21 +42,27 @@ func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) ( } } - w.Header().Set("Cache-Control", GetCacheControl()) w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat)) return false } -// HandleEtagCache handles ETag-based caching for a HTTP request -func HandleEtagCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) { +// HandleFileEtagCache handles ETag-based caching for a HTTP request +func HandleFileEtagCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) { + w.Header().Set("Cache-Control", GetCacheControl()) + etag := generateETag(fi) - if req.Header.Get("If-None-Match") == etag { - w.Header().Set("ETag", etag) - w.WriteHeader(http.StatusNotModified) - return true - } + return HandleGenericETagCache(req, w, etag) +} - w.Header().Set("Cache-Control", GetCacheControl()) - w.Header().Set("ETag", etag) +// HandleGenericETagCache handles ETag-based caching for a HTTP request. +// It returns true if the request was handled. +func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag string) bool { + if len(etag) > 0 { + w.Header().Set("Etag", etag) + if req.Header.Get("If-None-Match") == etag { + w.WriteHeader(http.StatusNotModified) + return true + } + } return false } diff --git a/modules/public/public.go b/modules/public/public.go index ee3d2cf75f73..ea87cbc7fc06 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -165,7 +165,7 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, opt *Optio log.Println("[Static] Serving " + file) } - if httpcache.HandleEtagCache(req, w, fi) { + if httpcache.HandleFileEtagCache(req, w, fi) { return true } diff --git a/routers/repo/attachment.go b/routers/repo/attachment.go index bf172cdf0f8d..dda72708b3a7 100644 --- a/routers/repo/attachment.go +++ b/routers/repo/attachment.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" @@ -139,8 +140,7 @@ func GetAttachment(ctx *context.Context) { } } - etag := `"` + attach.UUID + `"` - if handleETag(ctx, etag) { + if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+attach.UUID+`"`) { return } @@ -152,7 +152,7 @@ func GetAttachment(ctx *context.Context) { } defer fr.Close() - if err = ServeData(ctx, etag, attach.Name, attach.Size, fr); err != nil { + if err = ServeData(ctx, attach.Name, attach.Size, fr); err != nil { ctx.ServerError("ServeData", err) return } diff --git a/routers/repo/download.go b/routers/repo/download.go index 432518ee88ee..1eedec8cb177 100644 --- a/routers/repo/download.go +++ b/routers/repo/download.go @@ -8,7 +8,6 @@ package repo import ( "fmt" "io" - "net/http" "path" "strings" @@ -16,12 +15,13 @@ import ( "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" ) // ServeData download file from io.Reader -func ServeData(ctx *context.Context, etag, name string, size int64, reader io.Reader) error { +func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) error { buf := make([]byte, 1024) n, err := reader.Read(buf) if err != nil && err != io.EOF { @@ -32,9 +32,7 @@ func ServeData(ctx *context.Context, etag, name string, size int64, reader io.Re } ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400") - if len(etag) > 0 { - ctx.Resp.Header().Set("Etag", etag) - } + if size >= 0 { ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size)) } else { @@ -75,8 +73,7 @@ func ServeData(ctx *context.Context, etag, name string, size int64, reader io.Re // ServeBlob download a git.Blob func ServeBlob(ctx *context.Context, blob *git.Blob) error { - etag := `"` + blob.ID.String() + `"` - if handleETag(ctx, etag) { + if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) { return nil } @@ -90,12 +87,12 @@ func ServeBlob(ctx *context.Context, blob *git.Blob) error { } }() - return ServeData(ctx, etag, ctx.Repo.TreePath, blob.Size(), dataRc) + return ServeData(ctx, ctx.Repo.TreePath, blob.Size(), dataRc) } // ServeBlobOrLFS download a git.Blob redirecting to LFS if necessary func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error { - if handleETag(ctx, `"`+blob.ID.String()+`"`) { + if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) { return nil } @@ -115,8 +112,7 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error { if meta == nil { return ServeBlob(ctx, blob) } - etag := `"` + pointer.Oid + `"` - if handleETag(ctx, etag) { + if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`) { return nil } lfsDataRc, err := lfs.ReadMetaObject(meta.Pointer) @@ -128,22 +124,12 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error { log.Error("ServeBlobOrLFS: Close: %v", err) } }() - return ServeData(ctx, etag, ctx.Repo.TreePath, meta.Size, lfsDataRc) + return ServeData(ctx, ctx.Repo.TreePath, meta.Size, lfsDataRc) } return ServeBlob(ctx, blob) } -// handleETag returns true if the etag matches the request and sends StatusNotModified -func handleETag(ctx *context.Context, etag string) bool { - if ctx.Req.Header.Get("If-None-Match") == etag { - ctx.Resp.Header().Set("Etag", etag) - ctx.Resp.WriteHeader(http.StatusNotModified) - return true - } - return false -} - // SingleDownload download a file by repos path func SingleDownload(ctx *context.Context) { blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreePath) From cea6a3c8422b6eaea72fd69bdece64178028008e Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 9 Apr 2021 18:10:19 +0000 Subject: [PATCH 04/11] Changed name. --- routers/repo/attachment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/repo/attachment.go b/routers/repo/attachment.go index dda72708b3a7..f53e7450ae02 100644 --- a/routers/repo/attachment.go +++ b/routers/repo/attachment.go @@ -126,7 +126,7 @@ func GetAttachment(ctx *context.Context) { } if err := attach.IncreaseDownloadCount(); err != nil { - ctx.ServerError("Update", err) + ctx.ServerError("IncreaseDownloadCount", err) return } From da3a276283f07bfc9be307bc46f2c979b090141a Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 9 Apr 2021 18:16:34 +0000 Subject: [PATCH 05/11] Lint --- modules/httpcache/httpcache.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index bbd57e04c4f6..8ca7aa839625 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -48,8 +48,6 @@ func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) ( // HandleFileEtagCache handles ETag-based caching for a HTTP request func HandleFileEtagCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) { - w.Header().Set("Cache-Control", GetCacheControl()) - etag := generateETag(fi) return HandleGenericETagCache(req, w, etag) } @@ -64,5 +62,6 @@ func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag strin return true } } + w.Header().Set("Cache-Control", GetCacheControl()) return false } From bddd82bcdcfbb7e327beeb47f071adfdea73e3f4 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 9 Apr 2021 22:22:35 +0000 Subject: [PATCH 06/11] Implemented If-None-Match list. --- modules/httpcache/httpcache.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index 8ca7aa839625..e4d127eec796 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -10,6 +10,7 @@ import ( "net/http" "os" "strconv" + "strings" "time" "code.gitea.io/gitea/modules/setting" @@ -54,13 +55,23 @@ func HandleFileEtagCache(req *http.Request, w http.ResponseWriter, fi os.FileInf // HandleGenericETagCache handles ETag-based caching for a HTTP request. // It returns true if the request was handled. -func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag string) bool { +func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag string) (handled bool) { if len(etag) > 0 { w.Header().Set("Etag", etag) - if req.Header.Get("If-None-Match") == etag { - w.WriteHeader(http.StatusNotModified) + + ifNoneMatch := req.Header.Get("If-None-Match") + if ifNoneMatch == "*" { return true } + + etag = strings.TrimPrefix(etag, "W/") + for _, item := range strings.Split(ifNoneMatch, ",") { + item = strings.TrimPrefix(strings.TrimSpace(item), "W/") + if item == etag { + w.WriteHeader(http.StatusNotModified) + return true + } + } } w.Header().Set("Cache-Control", GetCacheControl()) return false From 8fef4e95a915b944ac113d83a0193fa079a12a06 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 10 Apr 2021 07:57:31 +0000 Subject: [PATCH 07/11] Fixed missing header on * --- modules/httpcache/httpcache.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index e4d127eec796..59572d1df696 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -58,8 +58,19 @@ func HandleFileEtagCache(req *http.Request, w http.ResponseWriter, fi os.FileInf func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag string) (handled bool) { if len(etag) > 0 { w.Header().Set("Etag", etag) + if checkIfNoneMatchIsValid(req, etag) { + w.WriteHeader(http.StatusNotModified) + return true + } + } + w.Header().Set("Cache-Control", GetCacheControl()) + return false +} - ifNoneMatch := req.Header.Get("If-None-Match") +// checkIfNoneMatchIsValid tests if the header If-None-Match matches the ETag +func checkIfNoneMatchIsValid(req *http.Request, etag string) bool { + ifNoneMatch := req.Header.Get("If-None-Match") + if len(ifNoneMatch) > 0 { if ifNoneMatch == "*" { return true } @@ -68,11 +79,9 @@ func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag strin for _, item := range strings.Split(ifNoneMatch, ",") { item = strings.TrimPrefix(strings.TrimSpace(item), "W/") if item == etag { - w.WriteHeader(http.StatusNotModified) return true } } } - w.Header().Set("Cache-Control", GetCacheControl()) return false } From d5918ccdec42d98be13b8a4131d30bc30d07b326 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 10 Apr 2021 11:28:25 +0000 Subject: [PATCH 08/11] Removed weak etag support. --- modules/httpcache/httpcache.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index 59572d1df696..d245a645c68d 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -75,9 +75,8 @@ func checkIfNoneMatchIsValid(req *http.Request, etag string) bool { return true } - etag = strings.TrimPrefix(etag, "W/") for _, item := range strings.Split(ifNoneMatch, ",") { - item = strings.TrimPrefix(strings.TrimSpace(item), "W/") + item = strings.TrimSpace(item) if item == etag { return true } From f4059ef51bb4b742490e98c14aec1e9537581697 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 10 Apr 2021 11:31:53 +0000 Subject: [PATCH 09/11] Removed * support. --- modules/httpcache/httpcache.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index d245a645c68d..c7e5f2c6c1f1 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -71,10 +71,6 @@ func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag strin func checkIfNoneMatchIsValid(req *http.Request, etag string) bool { ifNoneMatch := req.Header.Get("If-None-Match") if len(ifNoneMatch) > 0 { - if ifNoneMatch == "*" { - return true - } - for _, item := range strings.Split(ifNoneMatch, ",") { item = strings.TrimSpace(item) if item == etag { From f37853ef70ae0de130bd015b6fcefe5d878d060a Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 10 Apr 2021 13:12:50 +0000 Subject: [PATCH 10/11] Added unit test. --- modules/httpcache/httpcache.go | 4 +- modules/httpcache/httpcache_test.go | 158 ++++++++++++++++++++++++++++ modules/public/public.go | 2 +- 3 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 modules/httpcache/httpcache_test.go diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index c7e5f2c6c1f1..f5e3906be65c 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -47,8 +47,8 @@ func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) ( return false } -// HandleFileEtagCache handles ETag-based caching for a HTTP request -func HandleFileEtagCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) { +// HandleFileETagCache handles ETag-based caching for a HTTP request +func HandleFileETagCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) { etag := generateETag(fi) return HandleGenericETagCache(req, w, etag) } diff --git a/modules/httpcache/httpcache_test.go b/modules/httpcache/httpcache_test.go new file mode 100644 index 000000000000..439cfcd7f5c1 --- /dev/null +++ b/modules/httpcache/httpcache_test.go @@ -0,0 +1,158 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package httpcache + +import ( + "net/http" + "net/http/httptest" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +type mockFileInfo struct { +} + +func (m mockFileInfo) Name() string { return "gitea.test" } +func (m mockFileInfo) Size() int64 { return int64(10) } +func (m mockFileInfo) Mode() os.FileMode { return os.ModePerm } +func (m mockFileInfo) ModTime() time.Time { return time.Time{} } +func (m mockFileInfo) IsDir() bool { return false } +func (m mockFileInfo) Sys() interface{} { return nil } + +type mockResponseWriter struct { + header http.Header + StatusCode int +} + +func (m mockResponseWriter) Header() http.Header { + if m.header == nil { + m.header = make(http.Header) + } + return m.header +} +func (m mockResponseWriter) Write([]byte) (int, error) { return 0, nil } +func (m mockResponseWriter) WriteHeader(statusCode int) { m.StatusCode = statusCode } + +func TestHandleFileETagCache(t *testing.T) { + fi := mockFileInfo{} + etag := `"MTBnaXRlYS50ZXN0TW9uLCAwMSBKYW4gMDAwMSAwMDowMDowMCBHTVQ="` + + t.Run("No_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + handled := HandleFileETagCache(req, w, fi) + + assert.False(t, handled) + assert.Len(t, w.Header(), 2) + assert.Contains(t, w.Header(), "Cache-Control") + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + }) + t.Run("Wrong_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + req.Header.Set("If-None-Match", `"wrong etag"`) + + handled := HandleFileETagCache(req, w, fi) + + assert.False(t, handled) + assert.Len(t, w.Header(), 2) + assert.Contains(t, w.Header(), "Cache-Control") + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + }) + t.Run("Correct_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + req.Header.Set("If-None-Match", etag) + + handled := HandleFileETagCache(req, w, fi) + + assert.True(t, handled) + assert.Len(t, w.Header(), 1) + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + assert.Equal(t, http.StatusNotModified, w.Code) + }) +} + +func TestHandleGenericETagCache(t *testing.T) { + etag := `"test"` + + t.Run("No_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + handled := HandleGenericETagCache(req, w, etag) + + assert.False(t, handled) + assert.Len(t, w.Header(), 2) + assert.Contains(t, w.Header(), "Cache-Control") + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + }) + t.Run("Wrong_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + req.Header.Set("If-None-Match", `"wrong etag"`) + + handled := HandleGenericETagCache(req, w, etag) + + assert.False(t, handled) + assert.Len(t, w.Header(), 2) + assert.Contains(t, w.Header(), "Cache-Control") + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + }) + t.Run("Correct_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + req.Header.Set("If-None-Match", etag) + + handled := HandleGenericETagCache(req, w, etag) + + assert.True(t, handled) + assert.Len(t, w.Header(), 1) + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + assert.Equal(t, http.StatusNotModified, w.Code) + }) + t.Run("Multiple_Wrong_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + req.Header.Set("If-None-Match", `"wrong etag", "wrong etag "`) + + handled := HandleGenericETagCache(req, w, etag) + + assert.False(t, handled) + assert.Len(t, w.Header(), 2) + assert.Contains(t, w.Header(), "Cache-Control") + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + }) + t.Run("Multiple_Correct_If-None-Match", func(t *testing.T) { + req := &http.Request{Header: make(http.Header)} + w := httptest.NewRecorder() + + req.Header.Set("If-None-Match", `"wrong etag", `+etag) + + handled := HandleGenericETagCache(req, w, etag) + + assert.True(t, handled) + assert.Len(t, w.Header(), 1) + assert.Contains(t, w.Header(), "Etag") + assert.Equal(t, etag, w.Header().Get("Etag")) + assert.Equal(t, http.StatusNotModified, w.Code) + }) +} diff --git a/modules/public/public.go b/modules/public/public.go index ea87cbc7fc06..14525cfa0e67 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -165,7 +165,7 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, opt *Optio log.Println("[Static] Serving " + file) } - if httpcache.HandleFileEtagCache(req, w, fi) { + if httpcache.HandleFileETagCache(req, w, fi) { return true } From f9584e4d76fcfc5a415634072bc6b47949b07323 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 10 Apr 2021 13:34:39 +0000 Subject: [PATCH 11/11] Lint --- modules/httpcache/httpcache_test.go | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/modules/httpcache/httpcache_test.go b/modules/httpcache/httpcache_test.go index 439cfcd7f5c1..fe5ca179560f 100644 --- a/modules/httpcache/httpcache_test.go +++ b/modules/httpcache/httpcache_test.go @@ -17,26 +17,12 @@ import ( type mockFileInfo struct { } -func (m mockFileInfo) Name() string { return "gitea.test" } -func (m mockFileInfo) Size() int64 { return int64(10) } -func (m mockFileInfo) Mode() os.FileMode { return os.ModePerm } +func (m mockFileInfo) Name() string { return "gitea.test" } +func (m mockFileInfo) Size() int64 { return int64(10) } +func (m mockFileInfo) Mode() os.FileMode { return os.ModePerm } func (m mockFileInfo) ModTime() time.Time { return time.Time{} } -func (m mockFileInfo) IsDir() bool { return false } -func (m mockFileInfo) Sys() interface{} { return nil } - -type mockResponseWriter struct { - header http.Header - StatusCode int -} - -func (m mockResponseWriter) Header() http.Header { - if m.header == nil { - m.header = make(http.Header) - } - return m.header -} -func (m mockResponseWriter) Write([]byte) (int, error) { return 0, nil } -func (m mockResponseWriter) WriteHeader(statusCode int) { m.StatusCode = statusCode } +func (m mockFileInfo) IsDir() bool { return false } +func (m mockFileInfo) Sys() interface{} { return nil } func TestHandleFileETagCache(t *testing.T) { fi := mockFileInfo{}