From 71ade836bb2469afe6ca83b1abeaa1486b46e362 Mon Sep 17 00:00:00 2001
From: darkweak <darkweak@protonmail.com>
Date: Mon, 2 Sep 2024 16:09:39 +0200
Subject: [PATCH] fix(chore): surrogate use Content-Location instead of
 generated key

---
 .github/workflows/generate_release.sh         |  2 +-
 .github/workflows/release_plugins.yml         | 11 +++++
 pkg/middleware/middleware.go                  |  6 +--
 pkg/surrogate/providers/akamai.go             |  4 +-
 pkg/surrogate/providers/cloudflare.go         |  4 +-
 pkg/surrogate/providers/common.go             |  9 ++--
 pkg/surrogate/providers/common_test.go        | 12 ++---
 pkg/surrogate/providers/types.go              |  2 +-
 plugins/caddy/httpcache_test.go               | 48 +++++++++++++++++++
 .../examples/internal/handler/routes.go       |  2 +
 plugins/souin/storages/go.mod                 |  2 +-
 .../souin/pkg/surrogate/providers/akamai.go   |  4 +-
 .../pkg/surrogate/providers/cloudflare.go     |  4 +-
 .../souin/pkg/surrogate/providers/types.go    |  2 +-
 14 files changed, 88 insertions(+), 24 deletions(-)

diff --git a/.github/workflows/generate_release.sh b/.github/workflows/generate_release.sh
index 31c972a05..4e1df0880 100755
--- a/.github/workflows/generate_release.sh
+++ b/.github/workflows/generate_release.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-plugins=("beego" "caddy" "chi" "dotweb" "echo" "fiber" "gin" "go-zero" "goa" "goyave" "hertz" "kratos" "roadrunner" "skipper" "souin" "traefik" "tyk" "webgo")
+plugins=("beego" "caddy" "chi" "dotweb" "echo" "fiber" "gin" "go-zero" "goa" "goyave" "hertz" "kratos" "roadrunner" "skipper" "souin" "souin/storages" "traefik" "tyk" "webgo")
 
 IFS= read -r -d '' tpl <<EOF
 name: Tag submodules on release
diff --git a/.github/workflows/release_plugins.yml b/.github/workflows/release_plugins.yml
index d8e61ca95..d8be37200 100644
--- a/.github/workflows/release_plugins.yml
+++ b/.github/workflows/release_plugins.yml
@@ -177,6 +177,17 @@ jobs:
               ref: 'refs/tags/plugins/souin/${{ github.ref_name }}',
               sha: context.sha
             })
+      -
+        name: Create Souin/storages tag
+        uses: actions/github-script@v7
+        with:
+          script: |
+            github.rest.git.createRef({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              ref: 'refs/tags/plugins/souin/storages/${{ github.ref_name }}',
+              sha: context.sha
+            })
       -
         name: Create Traefik tag
         uses: actions/github-script@v7
diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go
index ad84376cc..8e02dbe76 100644
--- a/pkg/middleware/middleware.go
+++ b/pkg/middleware/middleware.go
@@ -353,9 +353,9 @@ func (s *SouinBaseHandler) Store(
 
 					wg.Wait()
 					if len(fails) < s.storersLen {
-						go func(rs http.Response, key string, basekey string) {
-							_ = s.SurrogateKeyStorer.Store(&rs, key, uri, basekey)
-						}(res, variedKey, cachedKey)
+						go func(rs http.Response, key string) {
+							_ = s.SurrogateKeyStorer.Store(&rs, key, uri)
+						}(res, variedKey)
 						status += "; stored"
 					}
 
diff --git a/pkg/surrogate/providers/akamai.go b/pkg/surrogate/providers/akamai.go
index d33455b79..1efef7298 100644
--- a/pkg/surrogate/providers/akamai.go
+++ b/pkg/surrogate/providers/akamai.go
@@ -39,12 +39,12 @@ func (*AkamaiSurrogateStorage) getHeaderSeparator() string {
 }
 
 // Store stores the response tags located in the first non empty supported header
-func (a *AkamaiSurrogateStorage) Store(response *http.Response, cacheKey, uri, basekey string) error {
+func (a *AkamaiSurrogateStorage) Store(response *http.Response, cacheKey, uri string) error {
 	defer func() {
 		response.Header.Del(surrogateKey)
 		response.Header.Del(surrogateControl)
 	}()
-	e := a.baseStorage.Store(response, cacheKey, uri, basekey)
+	e := a.baseStorage.Store(response, cacheKey, uri)
 	response.Header.Set(edgeCacheTag, response.Header.Get(surrogateKey))
 
 	return e
diff --git a/pkg/surrogate/providers/cloudflare.go b/pkg/surrogate/providers/cloudflare.go
index d733347b8..83a50f13f 100644
--- a/pkg/surrogate/providers/cloudflare.go
+++ b/pkg/surrogate/providers/cloudflare.go
@@ -38,12 +38,12 @@ func (*CloudflareSurrogateStorage) getHeaderSeparator() string {
 }
 
 // Store stores the response tags located in the first non empty supported header
-func (c *CloudflareSurrogateStorage) Store(response *http.Response, cacheKey, uri, basekey string) error {
+func (c *CloudflareSurrogateStorage) Store(response *http.Response, cacheKey, uri string) error {
 	defer func() {
 		response.Header.Del(surrogateKey)
 		response.Header.Del(surrogateControl)
 	}()
-	e := c.baseStorage.Store(response, cacheKey, uri, basekey)
+	e := c.baseStorage.Store(response, cacheKey, uri)
 	response.Header.Set(cacheTag, strings.Join(c.ParseHeaders(response.Header.Get(surrogateKey)), c.getHeaderSeparator()))
 
 	return e
diff --git a/pkg/surrogate/providers/common.go b/pkg/surrogate/providers/common.go
index 6b1e6b780..b5f2dcd5f 100644
--- a/pkg/surrogate/providers/common.go
+++ b/pkg/surrogate/providers/common.go
@@ -210,7 +210,7 @@ func (s *baseStorage) purgeTag(tag string) []string {
 }
 
 // Store will take the lead to store the cache key for each provided Surrogate-key
-func (s *baseStorage) Store(response *http.Response, cacheKey, uri, basekey string) error {
+func (s *baseStorage) Store(response *http.Response, cacheKey, uri string) error {
 	h := response.Header
 
 	cacheKey = url.QueryEscape(cacheKey)
@@ -238,8 +238,11 @@ func (s *baseStorage) Store(response *http.Response, cacheKey, uri, basekey stri
 		}
 	}
 
-	urlRegexp = regexp.MustCompile("(^|" + regexp.QuoteMeta(souinStorageSeparator) + ")" + regexp.QuoteMeta(basekey) + "(" + regexp.QuoteMeta(souinStorageSeparator) + "|$)")
-	s.storeTag(uri, basekey, urlRegexp)
+	if h.Get("Content-Location") != "" {
+		location := h.Get("Content-Location")
+		urlRegexp = regexp.MustCompile("(^|" + regexp.QuoteMeta(souinStorageSeparator) + ")" + regexp.QuoteMeta(location) + "(" + regexp.QuoteMeta(souinStorageSeparator) + "|$)")
+		s.storeTag(uri, location, urlRegexp)
+	}
 	s.storeTag(uri, cacheKey, urlRegexp)
 
 	return nil
diff --git a/pkg/surrogate/providers/common_test.go b/pkg/surrogate/providers/common_test.go
index 06fb90d66..e1870234f 100644
--- a/pkg/surrogate/providers/common_test.go
+++ b/pkg/surrogate/providers/common_test.go
@@ -106,7 +106,7 @@ func TestBaseStorage_Store(t *testing.T) {
 
 	bs := mockCommonProvider()
 
-	e := bs.Store(&res, "((((invalid_key_but_escaped", "", "")
+	e := bs.Store(&res, "((((invalid_key_but_escaped", "")
 	if e != nil {
 		t.Error("It shouldn't throw an error with a valid key.")
 	}
@@ -116,7 +116,7 @@ func TestBaseStorage_Store(t *testing.T) {
 	_ = bs.Storage.Set("test5", []byte("first,second,fifth"), storageToInfiniteTTLMap[bs.Storage.Name()])
 	_ = bs.Storage.Set("testInvalid", []byte("invalid"), storageToInfiniteTTLMap[bs.Storage.Name()])
 
-	if e = bs.Store(&res, "stored", "", ""); e != nil {
+	if e = bs.Store(&res, "stored", ""); e != nil {
 		t.Error("It shouldn't throw an error with a valid key.")
 	}
 
@@ -133,10 +133,10 @@ func TestBaseStorage_Store(t *testing.T) {
 	}
 
 	res.Header.Set(surrogateKey, "something")
-	_ = bs.Store(&res, "/something", "", "")
-	_ = bs.Store(&res, "/something", "", "")
+	_ = bs.Store(&res, "/something", "")
+	_ = bs.Store(&res, "/something", "")
 	res.Header.Set(surrogateKey, "something")
-	_ = bs.Store(&res, "/some", "", "")
+	_ = bs.Store(&res, "/some", "")
 
 	_ = len(bs.Storage.MapKeys(surrogatePrefix))
 	// if storageSize != 6 {
@@ -161,7 +161,7 @@ func TestBaseStorage_Store_Load(t *testing.T) {
 		wg.Add(1)
 		go func(r http.Response, iteration int, group *sync.WaitGroup) {
 			defer wg.Done()
-			_ = bs.Store(&r, fmt.Sprintf("my_dynamic_cache_key_%d", iteration), "", "")
+			_ = bs.Store(&r, fmt.Sprintf("my_dynamic_cache_key_%d", iteration), "")
 		}(res, i, &wg)
 	}
 
diff --git a/pkg/surrogate/providers/types.go b/pkg/surrogate/providers/types.go
index 999fe90ff..9f012ddb2 100644
--- a/pkg/surrogate/providers/types.go
+++ b/pkg/surrogate/providers/types.go
@@ -16,7 +16,7 @@ type SurrogateInterface interface {
 	Purge(http.Header) (cacheKeys []string, surrogateKeys []string)
 	Invalidate(method string, h http.Header)
 	purgeTag(string) []string
-	Store(*http.Response, string, string, string) error
+	Store(*http.Response, string, string) error
 	storeTag(string, string, *regexp.Regexp)
 	ParseHeaders(string) []string
 	List() map[string]string
diff --git a/plugins/caddy/httpcache_test.go b/plugins/caddy/httpcache_test.go
index d7258c014..c5eed1f3e 100644
--- a/plugins/caddy/httpcache_test.go
+++ b/plugins/caddy/httpcache_test.go
@@ -1038,3 +1038,51 @@ func TestExpires(t *testing.T) {
 	cacheChecker(caddyTester, "/expires-with-max-age", "Hello, expires-with-max-age!", 59)
 	cacheChecker(caddyTester, "/expires-with-s-maxage", "Hello, expires-with-s-maxage!", 4)
 }
+
+func TestComplexQuery(t *testing.T) {
+	caddyTester := caddytest.NewTester(t)
+	caddyTester.InitServer(`
+	{
+		admin localhost:2999
+		http_port     9080
+		https_port    9443
+		cache {
+			ttl 10s
+		}
+	}
+	localhost:9080 {
+		route /complex-query {
+			cache
+			respond "Hello, {query}!"
+		}
+	}`, "caddyfile")
+
+	cacheChecker := func(tester *caddytest.Tester, query string, expectedDuration int) {
+		body := fmt.Sprintf("Hello, %s!", query)
+		resp1, _ := tester.AssertGetResponse("http://localhost:9080/complex-query?"+query, 200, body)
+		if resp1.Header.Get("Age") != "" {
+			t.Errorf("unexpected Age header %v", resp1.Header.Get("Age"))
+		}
+
+		if resp1.Header.Get("Cache-Status") != "Souin; fwd=uri-miss; stored; key=GET-http-localhost:9080-/complex-query?"+query {
+			t.Errorf("unexpected first Cache-Status header %v", resp1.Header.Get("Cache-Status"))
+		}
+
+		resp1, _ = tester.AssertGetResponse("http://localhost:9080/complex-query?"+query, 200, body)
+
+		if resp1.Header.Get("Age") != "1" {
+			t.Errorf("unexpected Age header %v", resp1.Header.Get("Age"))
+		}
+
+		if resp1.Header.Get("Cache-Status") != fmt.Sprintf("Souin; hit; ttl=%d; key=GET-http-localhost:9080-/complex-query?%s; detail=DEFAULT", expectedDuration, query) {
+			t.Errorf(
+				"unexpected second Cache-Status header %v, expected %s",
+				resp1.Header.Get("Cache-Status"),
+				fmt.Sprintf("Souin; hit; ttl=%d; key=GET-http-localhost:9080-/complex-query?%s; detail=DEFAULT", expectedDuration, query),
+			)
+		}
+	}
+
+	cacheChecker(caddyTester, "fields[]=id&pagination=true", 9)
+	cacheChecker(caddyTester, "fields[]=id&pagination=false", 9)
+}
diff --git a/plugins/go-zero/examples/internal/handler/routes.go b/plugins/go-zero/examples/internal/handler/routes.go
index 8456f428e..bcdd43890 100644
--- a/plugins/go-zero/examples/internal/handler/routes.go
+++ b/plugins/go-zero/examples/internal/handler/routes.go
@@ -1,4 +1,6 @@
 // Code generated by goctl. DO NOT EDIT.
+// goctl 1.7.1
+
 package handler
 
 import (
diff --git a/plugins/souin/storages/go.mod b/plugins/souin/storages/go.mod
index 50f32e638..d26ed4037 100644
--- a/plugins/souin/storages/go.mod
+++ b/plugins/souin/storages/go.mod
@@ -5,7 +5,7 @@ go 1.22.1
 replace github.com/darkweak/souin => ../../..
 
 require (
-	github.com/darkweak/souin v1.6.49
+	github.com/darkweak/souin v1.6.50
 	github.com/darkweak/storages/badger v0.0.8
 	github.com/darkweak/storages/core v0.0.8
 	github.com/darkweak/storages/etcd v0.0.8
diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/surrogate/providers/akamai.go b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/surrogate/providers/akamai.go
index d33455b79..1efef7298 100644
--- a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/surrogate/providers/akamai.go
+++ b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/surrogate/providers/akamai.go
@@ -39,12 +39,12 @@ func (*AkamaiSurrogateStorage) getHeaderSeparator() string {
 }
 
 // Store stores the response tags located in the first non empty supported header
-func (a *AkamaiSurrogateStorage) Store(response *http.Response, cacheKey, uri, basekey string) error {
+func (a *AkamaiSurrogateStorage) Store(response *http.Response, cacheKey, uri string) error {
 	defer func() {
 		response.Header.Del(surrogateKey)
 		response.Header.Del(surrogateControl)
 	}()
-	e := a.baseStorage.Store(response, cacheKey, uri, basekey)
+	e := a.baseStorage.Store(response, cacheKey, uri)
 	response.Header.Set(edgeCacheTag, response.Header.Get(surrogateKey))
 
 	return e
diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/surrogate/providers/cloudflare.go b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/surrogate/providers/cloudflare.go
index d733347b8..83a50f13f 100644
--- a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/surrogate/providers/cloudflare.go
+++ b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/surrogate/providers/cloudflare.go
@@ -38,12 +38,12 @@ func (*CloudflareSurrogateStorage) getHeaderSeparator() string {
 }
 
 // Store stores the response tags located in the first non empty supported header
-func (c *CloudflareSurrogateStorage) Store(response *http.Response, cacheKey, uri, basekey string) error {
+func (c *CloudflareSurrogateStorage) Store(response *http.Response, cacheKey, uri string) error {
 	defer func() {
 		response.Header.Del(surrogateKey)
 		response.Header.Del(surrogateControl)
 	}()
-	e := c.baseStorage.Store(response, cacheKey, uri, basekey)
+	e := c.baseStorage.Store(response, cacheKey, uri)
 	response.Header.Set(cacheTag, strings.Join(c.ParseHeaders(response.Header.Get(surrogateKey)), c.getHeaderSeparator()))
 
 	return e
diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/surrogate/providers/types.go b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/surrogate/providers/types.go
index 999fe90ff..9f012ddb2 100644
--- a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/surrogate/providers/types.go
+++ b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/surrogate/providers/types.go
@@ -16,7 +16,7 @@ type SurrogateInterface interface {
 	Purge(http.Header) (cacheKeys []string, surrogateKeys []string)
 	Invalidate(method string, h http.Header)
 	purgeTag(string) []string
-	Store(*http.Response, string, string, string) error
+	Store(*http.Response, string, string) error
 	storeTag(string, string, *regexp.Regexp)
 	ParseHeaders(string) []string
 	List() map[string]string