Skip to content

Commit 68586c8

Browse files
committed
resources: Add responseHeaders option to resources.GetRemote
* These response headers will be included in `.Data.Headers` if found. * The header name matching is case insensitive. * `Data.Headers` is of type `map[string][]string` * In most cases there will be only one value per header key, but e.g. `Set-Cookie` commonly has multiple values. Fixes #12521
1 parent 51bb2fe commit 68586c8

File tree

2 files changed

+57
-10
lines changed

2 files changed

+57
-10
lines changed

resources/resource_factories/create/create_integration_test.go

+36-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,42 @@ func TestGetRemoteHead(t *testing.T) {
5656

5757
b.AssertFileContent("public/index.html",
5858
"Head Content: .",
59-
"Head Data: map[ContentLength:18210 ContentType:image/png Status:200 OK StatusCode:200 TransferEncoding:[]]",
59+
"Head Data: map[ContentLength:18210 ContentType:image/png Headers:map[] Status:200 OK StatusCode:200 TransferEncoding:[]]",
60+
)
61+
}
62+
63+
func TestGetRemoteResponseHeaders(t *testing.T) {
64+
files := `
65+
-- config.toml --
66+
[security]
67+
[security.http]
68+
methods = ['(?i)GET|POST|HEAD']
69+
urls = ['.*gohugo\.io.*']
70+
-- layouts/index.html --
71+
{{ $url := "https://gohugo.io/img/hugo.png" }}
72+
{{ $opts := dict "method" "head" "responseHeaders" (slice "X-Frame-Options" "Server") }}
73+
{{ with try (resources.GetRemote $url $opts) }}
74+
{{ with .Err }}
75+
{{ errorf "Unable to get remote resource: %s" . }}
76+
{{ else with .Value }}
77+
Response Headers: {{ .Data.Headers }}
78+
{{ else }}
79+
{{ errorf "Unable to get remote resource: %s" $url }}
80+
{{ end }}
81+
{{ end }}
82+
`
83+
84+
b := hugolib.NewIntegrationTestBuilder(
85+
hugolib.IntegrationTestConfig{
86+
T: t,
87+
TxtarString: files,
88+
},
89+
)
90+
91+
b.Build()
92+
93+
b.AssertFileContent("public/index.html",
94+
"Response Headers: map[Server:[Netlify] X-Frame-Options:[DENY]]",
6095
)
6196
}
6297

resources/resource_factories/create/remote.go

+21-9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030

3131
"github.com/gohugoio/httpcache"
3232
"github.com/gohugoio/hugo/common/hashing"
33+
"github.com/gohugoio/hugo/common/hstrings"
3334
"github.com/gohugoio/hugo/common/hugio"
3435
"github.com/gohugoio/hugo/common/loggers"
3536
"github.com/gohugoio/hugo/common/maps"
@@ -51,18 +52,28 @@ type HTTPError struct {
5152
Body string
5253
}
5354

54-
func responseToData(res *http.Response, readBody bool) map[string]any {
55+
func responseToData(res *http.Response, readBody bool, includeHeaders []string) map[string]any {
5556
var body []byte
5657
if readBody {
5758
body, _ = io.ReadAll(res.Body)
5859
}
5960

61+
responseHeaders := make(map[string][]string)
62+
if true || len(includeHeaders) > 0 {
63+
for k, v := range res.Header {
64+
if hstrings.InSlicEqualFold(includeHeaders, k) {
65+
responseHeaders[k] = v
66+
}
67+
}
68+
}
69+
6070
m := map[string]any{
6171
"StatusCode": res.StatusCode,
6272
"Status": res.Status,
6373
"TransferEncoding": res.TransferEncoding,
6474
"ContentLength": res.ContentLength,
6575
"ContentType": res.Header.Get("Content-Type"),
76+
"Headers": responseHeaders,
6677
}
6778

6879
if readBody {
@@ -72,7 +83,7 @@ func responseToData(res *http.Response, readBody bool) map[string]any {
7283
return m
7384
}
7485

75-
func toHTTPError(err error, res *http.Response, readBody bool) *HTTPError {
86+
func toHTTPError(err error, res *http.Response, readBody bool, responseHeaders []string) *HTTPError {
7687
if err == nil {
7788
panic("err is nil")
7889
}
@@ -85,7 +96,7 @@ func toHTTPError(err error, res *http.Response, readBody bool) *HTTPError {
8596

8697
return &HTTPError{
8798
error: err,
88-
Data: responseToData(res, readBody),
99+
Data: responseToData(res, readBody, responseHeaders),
89100
}
90101
}
91102

@@ -213,7 +224,7 @@ func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resou
213224
}
214225

215226
if res.StatusCode < 200 || res.StatusCode > 299 {
216-
return nil, toHTTPError(fmt.Errorf("failed to fetch remote resource from '%s': %s", uri, http.StatusText(res.StatusCode)), res, !isHeadMethod)
227+
return nil, toHTTPError(fmt.Errorf("failed to fetch remote resource from '%s': %s", uri, http.StatusText(res.StatusCode)), res, !isHeadMethod, options.ResponseHeaders)
217228
}
218229

219230
var (
@@ -280,7 +291,7 @@ func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resou
280291
}
281292

282293
userKey = filename[:len(filename)-len(path.Ext(filename))] + "_" + userKey + mediaType.FirstSuffix.FullSuffix
283-
data := responseToData(res, false)
294+
data := responseToData(res, false, options.ResponseHeaders)
284295

285296
return c.rs.NewResource(
286297
resources.ResourceSourceDescriptor{
@@ -345,9 +356,10 @@ func hasHeaderKey(m http.Header, key string) bool {
345356
}
346357

347358
type fromRemoteOptions struct {
348-
Method string
349-
Headers map[string]any
350-
Body []byte
359+
Method string
360+
Headers map[string]any
361+
Body []byte
362+
ResponseHeaders []string
351363
}
352364

353365
func (o fromRemoteOptions) BodyReader() io.Reader {
@@ -432,7 +444,7 @@ func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error
432444
if resp != nil {
433445
msg = resp.Status
434446
}
435-
err := toHTTPError(fmt.Errorf("retry timeout (configured to %s) fetching remote resource: %s", t.Cfg.Timeout(), msg), resp, req.Method != "HEAD")
447+
err := toHTTPError(fmt.Errorf("retry timeout (configured to %s) fetching remote resource: %s", t.Cfg.Timeout(), msg), resp, req.Method != "HEAD", nil)
436448
return resp, err
437449
}
438450
time.Sleep(nextSleep)

0 commit comments

Comments
 (0)