url.go (4430B)
1 // Copyright 2021 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package paths
15
16 import (
17 "fmt"
18 "net/url"
19 "path"
20 "path/filepath"
21 "strings"
22 )
23
24 type pathBridge struct{}
25
26 func (pathBridge) Base(in string) string {
27 return path.Base(in)
28 }
29
30 func (pathBridge) Clean(in string) string {
31 return path.Clean(in)
32 }
33
34 func (pathBridge) Dir(in string) string {
35 return path.Dir(in)
36 }
37
38 func (pathBridge) Ext(in string) string {
39 return path.Ext(in)
40 }
41
42 func (pathBridge) Join(elem ...string) string {
43 return path.Join(elem...)
44 }
45
46 func (pathBridge) Separator() string {
47 return "/"
48 }
49
50 var pb pathBridge
51
52 // MakePermalink combines base URL with content path to create full URL paths.
53 // Example
54 // base: http://spf13.com/
55 // path: post/how-i-blog
56 // result: http://spf13.com/post/how-i-blog
57 func MakePermalink(host, plink string) *url.URL {
58 base, err := url.Parse(host)
59 if err != nil {
60 panic(err)
61 }
62
63 p, err := url.Parse(plink)
64 if err != nil {
65 panic(err)
66 }
67
68 if p.Host != "" {
69 panic(fmt.Errorf("can't make permalink from absolute link %q", plink))
70 }
71
72 base.Path = path.Join(base.Path, p.Path)
73
74 // path.Join will strip off the last /, so put it back if it was there.
75 hadTrailingSlash := (plink == "" && strings.HasSuffix(host, "/")) || strings.HasSuffix(p.Path, "/")
76 if hadTrailingSlash && !strings.HasSuffix(base.Path, "/") {
77 base.Path = base.Path + "/"
78 }
79
80 return base
81 }
82
83 // AddContextRoot adds the context root to an URL if it's not already set.
84 // For relative URL entries on sites with a base url with a context root set (i.e. http://example.com/mysite),
85 // relative URLs must not include the context root if canonifyURLs is enabled. But if it's disabled, it must be set.
86 func AddContextRoot(baseURL, relativePath string) string {
87 url, err := url.Parse(baseURL)
88 if err != nil {
89 panic(err)
90 }
91
92 newPath := path.Join(url.Path, relativePath)
93
94 // path strips trailing slash, ignore root path.
95 if newPath != "/" && strings.HasSuffix(relativePath, "/") {
96 newPath += "/"
97 }
98 return newPath
99 }
100
101 // URLizeAn
102
103 // PrettifyURL takes a URL string and returns a semantic, clean URL.
104 func PrettifyURL(in string) string {
105 x := PrettifyURLPath(in)
106
107 if path.Base(x) == "index.html" {
108 return path.Dir(x)
109 }
110
111 if in == "" {
112 return "/"
113 }
114
115 return x
116 }
117
118 // PrettifyURLPath takes a URL path to a content and converts it
119 // to enable pretty URLs.
120 // /section/name.html becomes /section/name/index.html
121 // /section/name/ becomes /section/name/index.html
122 // /section/name/index.html becomes /section/name/index.html
123 func PrettifyURLPath(in string) string {
124 return prettifyPath(in, pb)
125 }
126
127 // Uglify does the opposite of PrettifyURLPath().
128 // /section/name/index.html becomes /section/name.html
129 // /section/name/ becomes /section/name.html
130 // /section/name.html becomes /section/name.html
131 func Uglify(in string) string {
132 if path.Ext(in) == "" {
133 if len(in) < 2 {
134 return "/"
135 }
136 // /section/name/ -> /section/name.html
137 return path.Clean(in) + ".html"
138 }
139
140 name, ext := fileAndExt(in, pb)
141 if name == "index" {
142 // /section/name/index.html -> /section/name.html
143 d := path.Dir(in)
144 if len(d) > 1 {
145 return d + ext
146 }
147 return in
148 }
149 // /.xml -> /index.xml
150 if name == "" {
151 return path.Dir(in) + "index" + ext
152 }
153 // /section/name.html -> /section/name.html
154 return path.Clean(in)
155 }
156
157 // UrlToFilename converts the URL s to a filename.
158 // If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
159 func UrlToFilename(s string) (string, bool) {
160 u, err := url.ParseRequestURI(s)
161
162 if err != nil {
163 return filepath.FromSlash(s), false
164 }
165
166 p := u.Path
167
168 if p == "" {
169 p, _ = url.QueryUnescape(u.Opaque)
170 return filepath.FromSlash(p), true
171 }
172
173 p = filepath.FromSlash(p)
174
175 if u.Host != "" {
176 // C:\data\file.txt
177 p = strings.ToUpper(u.Host) + ":" + p
178 }
179
180 return p, true
181 }