diff options
Diffstat (limited to 'common/urls')
-rw-r--r-- | common/urls/baseURL.go | 110 | ||||
-rw-r--r-- | common/urls/baseURL_test.go | 67 |
2 files changed, 177 insertions, 0 deletions
diff --git a/common/urls/baseURL.go b/common/urls/baseURL.go new file mode 100644 index 000000000..df26730ec --- /dev/null +++ b/common/urls/baseURL.go @@ -0,0 +1,110 @@ +// Copyright 2023 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 urls + +import ( + "fmt" + "net/url" + "strconv" + "strings" +) + +// A BaseURL in Hugo is normally on the form scheme://path, but the +// form scheme: is also valid (mailto:hugo@rules.com). +type BaseURL struct { + url *url.URL + WithPath string + WithoutPath string + BasePath string +} + +func (b BaseURL) String() string { + return b.WithPath +} + +func (b BaseURL) Path() string { + return b.url.Path +} + +func (b BaseURL) Port() int { + p, _ := strconv.Atoi(b.url.Port()) + return p +} + +// HostURL returns the URL to the host root without any path elements. +func (b BaseURL) HostURL() string { + return strings.TrimSuffix(b.String(), b.Path()) +} + +// WithProtocol returns the BaseURL prefixed with the given protocol. +// The Protocol is normally of the form "scheme://", i.e. "webcal://". +func (b BaseURL) WithProtocol(protocol string) (BaseURL, error) { + u := b.URL() + + scheme := protocol + isFullProtocol := strings.HasSuffix(scheme, "://") + isOpaqueProtocol := strings.HasSuffix(scheme, ":") + + if isFullProtocol { + scheme = strings.TrimSuffix(scheme, "://") + } else if isOpaqueProtocol { + scheme = strings.TrimSuffix(scheme, ":") + } + + u.Scheme = scheme + + if isFullProtocol && u.Opaque != "" { + u.Opaque = "//" + u.Opaque + } else if isOpaqueProtocol && u.Opaque == "" { + return BaseURL{}, fmt.Errorf("cannot determine BaseURL for protocol %q", protocol) + } + + return newBaseURLFromURL(u) +} + +func (b BaseURL) WithPort(port int) (BaseURL, error) { + u := b.URL() + u.Host = u.Hostname() + ":" + strconv.Itoa(port) + return newBaseURLFromURL(u) +} + +// URL returns a copy of the internal URL. +// The copy can be safely used and modified. +func (b BaseURL) URL() *url.URL { + c := *b.url + return &c +} + +func NewBaseURLFromString(b string) (BaseURL, error) { + u, err := url.Parse(b) + if err != nil { + return BaseURL{}, err + } + return newBaseURLFromURL(u) + +} + +func newBaseURLFromURL(u *url.URL) (BaseURL, error) { + baseURL := BaseURL{url: u, WithPath: u.String()} + var baseURLNoPath = baseURL.URL() + baseURLNoPath.Path = "" + baseURL.WithoutPath = baseURLNoPath.String() + + basePath := u.Path + if basePath != "" && basePath != "/" { + baseURL.BasePath = basePath + } + + return baseURL, nil +} diff --git a/common/urls/baseURL_test.go b/common/urls/baseURL_test.go new file mode 100644 index 000000000..95dc73339 --- /dev/null +++ b/common/urls/baseURL_test.go @@ -0,0 +1,67 @@ +// Copyright 2023 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 urls + +import ( + "testing" + + qt "github.com/frankban/quicktest" +) + +func TestBaseURL(t *testing.T) { + c := qt.New(t) + b, err := NewBaseURLFromString("http://example.com") + c.Assert(err, qt.IsNil) + c.Assert(b.String(), qt.Equals, "http://example.com") + + p, err := b.WithProtocol("webcal://") + c.Assert(err, qt.IsNil) + c.Assert(p.String(), qt.Equals, "webcal://example.com") + + p, err = b.WithProtocol("webcal") + c.Assert(err, qt.IsNil) + c.Assert(p.String(), qt.Equals, "webcal://example.com") + + _, err = b.WithProtocol("mailto:") + c.Assert(err, qt.Not(qt.IsNil)) + + b, err = NewBaseURLFromString("mailto:hugo@rules.com") + c.Assert(err, qt.IsNil) + c.Assert(b.String(), qt.Equals, "mailto:hugo@rules.com") + + // These are pretty constructed + p, err = b.WithProtocol("webcal") + c.Assert(err, qt.IsNil) + c.Assert(p.String(), qt.Equals, "webcal:hugo@rules.com") + + p, err = b.WithProtocol("webcal://") + c.Assert(err, qt.IsNil) + c.Assert(p.String(), qt.Equals, "webcal://hugo@rules.com") + + // Test with "non-URLs". Some people will try to use these as a way to get + // relative URLs working etc. + b, err = NewBaseURLFromString("/") + c.Assert(err, qt.IsNil) + c.Assert(b.String(), qt.Equals, "/") + + b, err = NewBaseURLFromString("") + c.Assert(err, qt.IsNil) + c.Assert(b.String(), qt.Equals, "") + + // BaseURL with sub path + b, err = NewBaseURLFromString("http://example.com/sub") + c.Assert(err, qt.IsNil) + c.Assert(b.String(), qt.Equals, "http://example.com/sub") + c.Assert(b.HostURL(), qt.Equals, "http://example.com") +} |