summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSoren L. Hansen <sorenisanerd@gmail.com>2024-04-04 13:31:20 -0700
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2024-04-05 15:57:02 +0200
commit4500b0e423a2c58e6e54526d0d02d2924cc774b6 (patch)
tree21ea70f023d7614c67d84f2dda8a841031b5db02
parent060cce0a910ffd9dc4e92efbe758fc5f187c3145 (diff)
resources/page: Escape hash sign in permalinks
When creating a link to a file with a `#` in the filename, the link gets truncated. This happens because the filename is eventaully passed to `url.Parse` which (correctly!) interprets the `#` as fragment separator. This commit escapes the `#` in the filename before creating the link. Fixes #4926 Fixes #8232 Fixes #12342 Co-authored-by: Joe Mooring <joe.mooring@veriphor.com>
-rw-r--r--resources/page/page_paths.go7
-rw-r--r--resources/page/path_integration_test.go55
2 files changed, 62 insertions, 0 deletions
diff --git a/resources/page/page_paths.go b/resources/page/page_paths.go
index 8052287c6..4826ed5f9 100644
--- a/resources/page/page_paths.go
+++ b/resources/page/page_paths.go
@@ -268,6 +268,13 @@ func CreateTargetPaths(d TargetPathDescriptor) (tp TargetPaths) {
tp.SubResourceBaseTarget = pb.PathDir()
tp.SubResourceBaseLink = pb.LinkDir()
}
+
+ // paths.{URL,Path}Escape rely on url.Parse which
+ // will consider # a fragment identifier, so it and
+ // and everything after it will be stripped from
+ // `link`, so we need to escape it first.
+ link = strings.ReplaceAll(link, "#", "%23")
+
if d.URL != "" {
tp.Link = paths.URLEscape(link)
} else {
diff --git a/resources/page/path_integration_test.go b/resources/page/path_integration_test.go
new file mode 100644
index 000000000..a1aa1d406
--- /dev/null
+++ b/resources/page/path_integration_test.go
@@ -0,0 +1,55 @@
+// Copyright 2024 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package page_test
+
+import (
+ "testing"
+
+ "github.com/gohugoio/hugo/hugolib"
+)
+
+// Issue 4926
+// Issue 8232
+// Issue 12342
+func TestHashSignInPermalink(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableKinds = ['section','rss','sitemap','taxonomy']
+[permalinks]
+s1 = '/:section/:slug'
+-- layouts/_default/list.html --
+{{ range site.Pages }}{{ .RelPermalink }}|{{ end }}
+-- layouts/_default/single.html --
+{{ .Title }}
+-- content/s1/p1.md --
+---
+title: p#1
+tags: test#tag#
+---
+-- content/s2/p#2.md --
+---
+title: p#2
+---
+`
+
+ b := hugolib.Test(t, files)
+
+ b.AssertFileExists("public/s1/p#1/index.html", true)
+ b.AssertFileExists("public/s2/p#2/index.html", true)
+ b.AssertFileExists("public/tags/test#tag#/index.html", true)
+
+ b.AssertFileContentExact("public/index.html", "/|/s1/p%231/|/s2/p%232/|/tags/test%23tag%23/|")
+}