diff --git a/commands/hugo.go b/commands/hugo.go index 175c6dc9..0bb15c3d 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -652,7 +652,7 @@ func (c *commandeer) rebuildSites(events []fsnotify.Event) error { if langPath != "" { langPath = langPath + "/" } - home := c.hugo.PathSpec.PrependBasePath("/" + langPath) + home := c.hugo.PathSpec.PrependBasePath("/"+langPath, false) visited[home] = true } diff --git a/helpers/pathspec.go b/helpers/pathspec.go index 847029f4..b82ebd99 100644 --- a/helpers/pathspec.go +++ b/helpers/pathspec.go @@ -69,11 +69,9 @@ func NewPathSpecWithBaseBaseFsProvided(fs *hugofs.Fs, cfg config.Provider, baseB ProcessingStats: NewProcessingStats(p.Lang()), } - if !ps.CanonifyURLs { - basePath := ps.BaseURL.Path() - if basePath != "" && basePath != "/" { - ps.BasePath = basePath - } + basePath := ps.BaseURL.Path() + if basePath != "" && basePath != "/" { + ps.BasePath = basePath } return ps, nil diff --git a/helpers/url.go b/helpers/url.go index f167fd3d..a24f05b1 100644 --- a/helpers/url.go +++ b/helpers/url.go @@ -283,15 +283,13 @@ func AddContextRoot(baseURL, relativePath string) string { } // PrependBasePath prepends any baseURL sub-folder to the given resource -// if canonifyURLs is disabled. -// If canonifyURLs is set, we will globally prepend the absURL with any sub-folder, -// so avoid doing anything here to avoid getting double paths. -func (p *PathSpec) PrependBasePath(rel string) string { - if p.BasePath != "" { +func (p *PathSpec) PrependBasePath(rel string, isAbs bool) string { + basePath := p.GetBasePath(!isAbs) + if basePath != "" { rel = filepath.ToSlash(rel) // Need to prepend any path from the baseURL hadSlash := strings.HasSuffix(rel, "/") - rel = path.Join(p.BasePath, rel) + rel = path.Join(basePath, rel) if hadSlash { rel += "/" } diff --git a/hugolib/page_output.go b/hugolib/page_output.go index 204f5ace..9a653120 100644 --- a/hugolib/page_output.go +++ b/hugolib/page_output.go @@ -316,5 +316,5 @@ func (o *OutputFormat) Permalink() string { // RelPermalink returns the relative permalink to this output format. func (o *OutputFormat) RelPermalink() string { rel := o.p.createRelativePermalinkForOutputFormat(o.f) - return o.p.s.PathSpec.PrependBasePath(rel) + return o.p.s.PathSpec.PrependBasePath(rel, false) } diff --git a/hugolib/page_paths.go b/hugolib/page_paths.go index 151507be..756a0f2f 100644 --- a/hugolib/page_paths.go +++ b/hugolib/page_paths.go @@ -144,7 +144,7 @@ func (p *Page) initURLs() error { // Any language code in the path will be added later. p.relTargetPathBase = strings.TrimPrefix(p.relTargetPathBase, prefix+"/") } - p.relPermalink = p.s.PathSpec.PrependBasePath(rel) + p.relPermalink = p.s.PathSpec.PrependBasePath(rel, false) p.layoutDescriptor = p.createLayoutDescriptor() return nil } diff --git a/hugolib/pagebundler_test.go b/hugolib/pagebundler_test.go index 53a1fcb0..ab047205 100644 --- a/hugolib/pagebundler_test.go +++ b/hugolib/pagebundler_test.go @@ -45,177 +45,184 @@ func TestPageBundlerSiteRegular(t *testing.T) { baseBaseURL := "https://example.com" for _, baseURLPath := range []string{"", "/hugo"} { - for _, ugly := range []bool{false, true} { - t.Run(fmt.Sprintf("ugly=%t,path=%s", ugly, baseURLPath), - func(t *testing.T) { - baseURL := baseBaseURL + baseURLPath - assert := require.New(t) - fs, cfg := newTestBundleSources(t) - cfg.Set("baseURL", baseURL) - assert.NoError(loadDefaultSettingsFor(cfg)) - assert.NoError(loadLanguageSettings(cfg, nil)) + for _, canonify := range []bool{false, true} { + for _, ugly := range []bool{false, true} { + t.Run(fmt.Sprintf("ugly=%t,canonify=%t,path=%s", ugly, canonify, baseURLPath), + func(t *testing.T) { + baseURL := baseBaseURL + baseURLPath + relURLBase := baseURLPath + if canonify { + relURLBase = "" + } + assert := require.New(t) + fs, cfg := newTestBundleSources(t) + cfg.Set("baseURL", baseURL) + cfg.Set("canonifyURLs", canonify) + assert.NoError(loadDefaultSettingsFor(cfg)) + assert.NoError(loadLanguageSettings(cfg, nil)) + + cfg.Set("permalinks", map[string]string{ + "a": ":sections/:filename", + "b": ":year/:slug/", + "c": ":sections/:slug", + "": ":filename/", + }) + + cfg.Set("outputFormats", map[string]interface{}{ + "CUSTOMO": map[string]interface{}{ + "mediaType": media.HTMLType, + "baseName": "cindex", + "path": "cpath", + }, + }) + + cfg.Set("outputs", map[string]interface{}{ + "home": []string{"HTML", "CUSTOMO"}, + "page": []string{"HTML", "CUSTOMO"}, + "section": []string{"HTML", "CUSTOMO"}, + }) + + cfg.Set("uglyURLs", ugly) + + s := buildSingleSite(t, deps.DepsCfg{Logger: loggers.NewWarningLogger(), Fs: fs, Cfg: cfg}, BuildCfg{}) + + th := testHelper{s.Cfg, s.Fs, t} + + assert.Len(s.RegularPages, 8) + + singlePage := s.getPage(KindPage, "a/1.md") + assert.Equal("", singlePage.BundleType()) + + assert.NotNil(singlePage) + assert.Equal(singlePage, s.getPage("page", "a/1")) + assert.Equal(singlePage, s.getPage("page", "1")) + + assert.Contains(singlePage.content(), "TheContent") + + if ugly { + assert.Equal(relURLBase+"/a/1.html", singlePage.RelPermalink()) + th.assertFileContent(filepath.FromSlash("/work/public/a/1.html"), "TheContent") + + } else { + assert.Equal(relURLBase+"/a/1/", singlePage.RelPermalink()) + th.assertFileContent(filepath.FromSlash("/work/public/a/1/index.html"), "TheContent") + } + + th.assertFileContent(filepath.FromSlash("/work/public/images/hugo-logo.png"), "content") + + // This should be just copied to destination. + th.assertFileContent(filepath.FromSlash("/work/public/assets/pic1.png"), "content") + + leafBundle1 := s.getPage(KindPage, "b/my-bundle/index.md") + assert.NotNil(leafBundle1) + assert.Equal("leaf", leafBundle1.BundleType()) + assert.Equal("b", leafBundle1.Section()) + sectionB := s.getPage(KindSection, "b") + assert.NotNil(sectionB) + home, _ := s.Info.Home() + assert.Equal("branch", home.BundleType()) + + // This is a root bundle and should live in the "home section" + // See https://github.com/gohugoio/hugo/issues/4332 + rootBundle := s.getPage(KindPage, "root") + assert.NotNil(rootBundle) + assert.True(rootBundle.Parent().IsHome()) + if ugly { + assert.Equal(relURLBase+"/root.html", rootBundle.RelPermalink()) + } else { + assert.Equal(relURLBase+"/root/", rootBundle.RelPermalink()) + } + + leafBundle2 := s.getPage(KindPage, "a/b/index.md") + assert.NotNil(leafBundle2) + unicodeBundle := s.getPage(KindPage, "c/bundle/index.md") + assert.NotNil(unicodeBundle) + + pageResources := leafBundle1.Resources.ByType(pageResourceType) + assert.Len(pageResources, 2) + firstPage := pageResources[0].(*Page) + secondPage := pageResources[1].(*Page) + assert.Equal(filepath.FromSlash("/work/base/b/my-bundle/1.md"), firstPage.pathOrTitle(), secondPage.pathOrTitle()) + assert.Contains(firstPage.content(), "TheContent") + assert.Equal(6, len(leafBundle1.Resources)) + + // Verify shortcode in bundled page + assert.Contains(secondPage.content(), filepath.FromSlash("MyShort in b/my-bundle/2.md")) + + // https://github.com/gohugoio/hugo/issues/4582 + assert.Equal(leafBundle1, firstPage.Parent()) + assert.Equal(leafBundle1, secondPage.Parent()) + + assert.Equal(firstPage, pageResources.GetMatch("1*")) + assert.Equal(secondPage, pageResources.GetMatch("2*")) + assert.Nil(pageResources.GetMatch("doesnotexist*")) + + imageResources := leafBundle1.Resources.ByType("image") + assert.Equal(3, len(imageResources)) + image := imageResources[0] + + altFormat := leafBundle1.OutputFormats().Get("CUSTOMO") + assert.NotNil(altFormat) + + assert.Equal(baseURL+"/2017/pageslug/c/logo.png", image.Permalink()) + + th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug/c/logo.png"), "content") + th.assertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug/c/logo.png"), "content") + + // Custom media type defined in site config. + assert.Len(leafBundle1.Resources.ByType("bepsays"), 1) + + relPermalinker := func(s string) string { + return fmt.Sprintf(s, relURLBase) + } + + permalinker := func(s string) string { + return fmt.Sprintf(s, baseURL) + } + + if permalinker == nil { + } + + if ugly { + assert.Equal(relURLBase+"/2017/pageslug.html", leafBundle1.RelPermalink()) + assert.Equal(baseURL+"/2017/pageslug.html", leafBundle1.Permalink()) + th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug.html"), + "TheContent", + relPermalinker("Sunset RelPermalink: %s/2017/pageslug/sunset1.jpg"), + permalinker("Sunset Permalink: %s/2017/pageslug/sunset1.jpg"), + "Thumb Width: 123", + "Thumb Name: my-sunset-1", + relPermalinker("Short Sunset RelPermalink: %s/2017/pageslug/sunset2.jpg"), + "Short Thumb Width: 56", + "1: Image Title: Sunset Galore 1", + "1: Image Params: map[myparam:My Sunny Param]", + "2: Image Title: Sunset Galore 2", + "2: Image Params: map[myparam:My Sunny Param]", + "1: Image myParam: Lower: My Sunny Param Caps: My Sunny Param", + ) + th.assertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug.html"), "TheContent") + + assert.Equal(relURLBase+"/a/b.html", leafBundle2.RelPermalink()) + + // 은행 + assert.Equal(relURLBase+"/c/%EC%9D%80%ED%96%89.html", unicodeBundle.RelPermalink()) + th.assertFileContent(filepath.FromSlash("/work/public/c/은행.html"), "Content for 은행") + th.assertFileContent(filepath.FromSlash("/work/public/c/은행/logo-은행.png"), "은행 PNG") + + } else { + assert.Equal(relURLBase+"/2017/pageslug/", leafBundle1.RelPermalink()) + assert.Equal(baseURL+"/2017/pageslug/", leafBundle1.Permalink()) + th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug/index.html"), "TheContent") + th.assertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug/cindex.html"), "TheContent") + th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug/index.html"), "Single Title") + th.assertFileContent(filepath.FromSlash("/work/public/root/index.html"), "Single Title") + + assert.Equal(relURLBase+"/a/b/", leafBundle2.RelPermalink()) + + } - cfg.Set("permalinks", map[string]string{ - "a": ":sections/:filename", - "b": ":year/:slug/", - "c": ":sections/:slug", - "": ":filename/", }) - - cfg.Set("outputFormats", map[string]interface{}{ - "CUSTOMO": map[string]interface{}{ - "mediaType": media.HTMLType, - "baseName": "cindex", - "path": "cpath", - }, - }) - - cfg.Set("outputs", map[string]interface{}{ - "home": []string{"HTML", "CUSTOMO"}, - "page": []string{"HTML", "CUSTOMO"}, - "section": []string{"HTML", "CUSTOMO"}, - }) - - cfg.Set("uglyURLs", ugly) - - s := buildSingleSite(t, deps.DepsCfg{Logger: loggers.NewWarningLogger(), Fs: fs, Cfg: cfg}, BuildCfg{}) - - th := testHelper{s.Cfg, s.Fs, t} - - assert.Len(s.RegularPages, 8) - - singlePage := s.getPage(KindPage, "a/1.md") - assert.Equal("", singlePage.BundleType()) - - assert.NotNil(singlePage) - assert.Equal(singlePage, s.getPage("page", "a/1")) - assert.Equal(singlePage, s.getPage("page", "1")) - - assert.Contains(singlePage.content(), "TheContent") - - if ugly { - assert.Equal(baseURLPath+"/a/1.html", singlePage.RelPermalink()) - th.assertFileContent(filepath.FromSlash("/work/public/a/1.html"), "TheContent") - - } else { - assert.Equal(baseURLPath+"/a/1/", singlePage.RelPermalink()) - th.assertFileContent(filepath.FromSlash("/work/public/a/1/index.html"), "TheContent") - } - - th.assertFileContent(filepath.FromSlash("/work/public/images/hugo-logo.png"), "content") - - // This should be just copied to destination. - th.assertFileContent(filepath.FromSlash("/work/public/assets/pic1.png"), "content") - - leafBundle1 := s.getPage(KindPage, "b/my-bundle/index.md") - assert.NotNil(leafBundle1) - assert.Equal("leaf", leafBundle1.BundleType()) - assert.Equal("b", leafBundle1.Section()) - sectionB := s.getPage(KindSection, "b") - assert.NotNil(sectionB) - home, _ := s.Info.Home() - assert.Equal("branch", home.BundleType()) - - // This is a root bundle and should live in the "home section" - // See https://github.com/gohugoio/hugo/issues/4332 - rootBundle := s.getPage(KindPage, "root") - assert.NotNil(rootBundle) - assert.True(rootBundle.Parent().IsHome()) - if ugly { - assert.Equal(baseURLPath+"/root.html", rootBundle.RelPermalink()) - } else { - assert.Equal(baseURLPath+"/root/", rootBundle.RelPermalink()) - } - - leafBundle2 := s.getPage(KindPage, "a/b/index.md") - assert.NotNil(leafBundle2) - unicodeBundle := s.getPage(KindPage, "c/bundle/index.md") - assert.NotNil(unicodeBundle) - - pageResources := leafBundle1.Resources.ByType(pageResourceType) - assert.Len(pageResources, 2) - firstPage := pageResources[0].(*Page) - secondPage := pageResources[1].(*Page) - assert.Equal(filepath.FromSlash("/work/base/b/my-bundle/1.md"), firstPage.pathOrTitle(), secondPage.pathOrTitle()) - assert.Contains(firstPage.content(), "TheContent") - assert.Equal(6, len(leafBundle1.Resources)) - - // Verify shortcode in bundled page - assert.Contains(secondPage.content(), filepath.FromSlash("MyShort in b/my-bundle/2.md")) - - // https://github.com/gohugoio/hugo/issues/4582 - assert.Equal(leafBundle1, firstPage.Parent()) - assert.Equal(leafBundle1, secondPage.Parent()) - - assert.Equal(firstPage, pageResources.GetMatch("1*")) - assert.Equal(secondPage, pageResources.GetMatch("2*")) - assert.Nil(pageResources.GetMatch("doesnotexist*")) - - imageResources := leafBundle1.Resources.ByType("image") - assert.Equal(3, len(imageResources)) - image := imageResources[0] - - altFormat := leafBundle1.OutputFormats().Get("CUSTOMO") - assert.NotNil(altFormat) - - assert.Equal(baseURL+"/2017/pageslug/c/logo.png", image.Permalink()) - - th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug/c/logo.png"), "content") - th.assertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug/c/logo.png"), "content") - - // Custom media type defined in site config. - assert.Len(leafBundle1.Resources.ByType("bepsays"), 1) - - relPermalinker := func(s string) string { - return fmt.Sprintf(s, baseURLPath) - } - - permalinker := func(s string) string { - return fmt.Sprintf(s, baseURL) - } - - if permalinker == nil { - } - - if ugly { - assert.Equal(baseURLPath+"/2017/pageslug.html", leafBundle1.RelPermalink()) - assert.Equal(baseURL+"/2017/pageslug.html", leafBundle1.Permalink()) - th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug.html"), - "TheContent", - relPermalinker("Sunset RelPermalink: %s/2017/pageslug/sunset1.jpg"), - permalinker("Sunset Permalink: %s/2017/pageslug/sunset1.jpg"), - "Thumb Width: 123", - "Thumb Name: my-sunset-1", - relPermalinker("Short Sunset RelPermalink: %s/2017/pageslug/sunset2.jpg"), - "Short Thumb Width: 56", - "1: Image Title: Sunset Galore 1", - "1: Image Params: map[myparam:My Sunny Param]", - "2: Image Title: Sunset Galore 2", - "2: Image Params: map[myparam:My Sunny Param]", - "1: Image myParam: Lower: My Sunny Param Caps: My Sunny Param", - ) - th.assertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug.html"), "TheContent") - - assert.Equal(baseURLPath+"/a/b.html", leafBundle2.RelPermalink()) - - // 은행 - assert.Equal(baseURLPath+"/c/%EC%9D%80%ED%96%89.html", unicodeBundle.RelPermalink()) - th.assertFileContent(filepath.FromSlash("/work/public/c/은행.html"), "Content for 은행") - th.assertFileContent(filepath.FromSlash("/work/public/c/은행/logo-은행.png"), "은행 PNG") - - } else { - assert.Equal(baseURLPath+"/2017/pageslug/", leafBundle1.RelPermalink()) - assert.Equal(baseURL+"/2017/pageslug/", leafBundle1.Permalink()) - th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug/index.html"), "TheContent") - th.assertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug/cindex.html"), "TheContent") - th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug/index.html"), "Single Title") - th.assertFileContent(filepath.FromSlash("/work/public/root/index.html"), "Single Title") - - assert.Equal(baseURLPath+"/a/b/", leafBundle2.RelPermalink()) - - } - - }) + } } } diff --git a/hugolib/pagination.go b/hugolib/pagination.go index c703f6c9..05846a6b 100644 --- a/hugolib/pagination.go +++ b/hugolib/pagination.go @@ -588,7 +588,7 @@ func newPaginationURLFactory(d targetPathDescriptor) paginationURLFactory { targetPath := createTargetPath(pathDescriptor) targetPath = strings.TrimSuffix(targetPath, d.Type.BaseFilename()) - link := d.PathSpec.PrependBasePath(targetPath) + link := d.PathSpec.PrependBasePath(targetPath, false) // Note: The targetPath is massaged with MakePathSanitized return d.PathSpec.URLizeFilename(link) } diff --git a/hugolib/paths/paths.go b/hugolib/paths/paths.go index 05d5019d..7de0d9ff 100644 --- a/hugolib/paths/paths.go +++ b/hugolib/paths/paths.go @@ -34,7 +34,6 @@ type Paths struct { BaseURL // If the baseURL contains a base path, e.g. https://example.com/docs, then "/docs" will be the BasePath. - // This will not be set if canonifyURLs is enabled. BasePath string // Directories @@ -192,6 +191,15 @@ func New(fs *hugofs.Fs, cfg config.Provider) (*Paths, error) { return p, nil } +// GetBasePath returns any path element in baseURL if needed. +func (p *Paths) GetBasePath(isRelativeURL bool) string { + if isRelativeURL && p.CanonifyURLs { + // The baseURL will be prepended later. + return "" + } + return p.BasePath +} + func (p *Paths) Lang() string { if p == nil || p.Language == nil { return "" diff --git a/resource/image_cache.go b/resource/image_cache.go index 22c86ea3..8cc626ca 100644 --- a/resource/image_cache.go +++ b/resource/image_cache.go @@ -75,7 +75,7 @@ func (c *imageCache) getOrCreate( parent *Image, conf imageConfig, createImage func() (*Image, image.Image, error)) (*Image, error) { relTarget := parent.relTargetPathFromConfig(conf) - key := parent.relTargetPathForRel(relTarget.path(), false, false) + key := parent.relTargetPathForRel(relTarget.path(), false, false, false) // First check the in-memory store, then the disk. c.mu.RLock() diff --git a/resource/resource.go b/resource/resource.go index 35051991..a8f9dde0 100644 --- a/resource/resource.go +++ b/resource/resource.go @@ -708,7 +708,7 @@ func (l *genericResource) publishIfNeeded() { func (l *genericResource) Permalink() string { l.publishIfNeeded() - return l.spec.PermalinkForBaseURL(l.relPermalinkForRel(l.relTargetDirFile.path()), l.spec.BaseURL.HostURL()) + return l.spec.PermalinkForBaseURL(l.relPermalinkForRel(l.relTargetDirFile.path(), true), l.spec.BaseURL.HostURL()) } func (l *genericResource) RelPermalink() string { @@ -717,11 +717,11 @@ func (l *genericResource) RelPermalink() string { } func (l *genericResource) relPermalinkFor(target string) string { - return l.relPermalinkForRel(target) + return l.relPermalinkForRel(target, false) } func (l *genericResource) permalinkFor(target string) string { - return l.spec.PermalinkForBaseURL(l.relPermalinkForRel(target), l.spec.BaseURL.HostURL()) + return l.spec.PermalinkForBaseURL(l.relPermalinkForRel(target, true), l.spec.BaseURL.HostURL()) } func (l *genericResource) relTargetPathsFor(target string) []string { @@ -766,23 +766,23 @@ func (l *genericResource) updateParams(params map[string]interface{}) { } } -func (l *genericResource) relPermalinkForRel(rel string) string { - return l.spec.PathSpec.URLizeFilename(l.relTargetPathForRel(rel, false, true)) +func (l *genericResource) relPermalinkForRel(rel string, isAbs bool) string { + return l.spec.PathSpec.URLizeFilename(l.relTargetPathForRel(rel, false, isAbs, true)) } func (l *genericResource) relTargetPathsForRel(rel string) []string { if len(l.baseTargetPathDirs) == 0 { - return []string{l.relTargetPathForRelAndBasePath(rel, "", false)} + return []string{l.relTargetPathForRelAndBasePath(rel, "", false, false)} } var targetPaths = make([]string, len(l.baseTargetPathDirs)) for i, dir := range l.baseTargetPathDirs { - targetPaths[i] = l.relTargetPathForRelAndBasePath(rel, dir, false) + targetPaths[i] = l.relTargetPathForRelAndBasePath(rel, dir, false, false) } return targetPaths } -func (l *genericResource) relTargetPathForRel(rel string, addBaseTargetPath, isURL bool) string { +func (l *genericResource) relTargetPathForRel(rel string, addBaseTargetPath, isAbs, isURL bool) string { if addBaseTargetPath && len(l.baseTargetPathDirs) > 1 { panic("multiple baseTargetPathDirs") } @@ -791,10 +791,10 @@ func (l *genericResource) relTargetPathForRel(rel string, addBaseTargetPath, isU basePath = l.baseTargetPathDirs[0] } - return l.relTargetPathForRelAndBasePath(rel, basePath, isURL) + return l.relTargetPathForRelAndBasePath(rel, basePath, isAbs, isURL) } -func (l *genericResource) relTargetPathForRelAndBasePath(rel, basePath string, isURL bool) string { +func (l *genericResource) relTargetPathForRelAndBasePath(rel, basePath string, isAbs, isURL bool) string { if l.targetPathBuilder != nil { rel = l.targetPathBuilder(rel) } @@ -811,8 +811,11 @@ func (l *genericResource) relTargetPathForRelAndBasePath(rel, basePath string, i rel = path.Join(l.baseOffset, rel) } - if isURL && l.spec.PathSpec.BasePath != "" { - rel = path.Join(l.spec.PathSpec.BasePath, rel) + if isURL { + bp := l.spec.PathSpec.GetBasePath(!isAbs) + if bp != "" { + rel = path.Join(bp, rel) + } } if len(rel) == 0 || rel[0] != '/' {