url.go - hugo - [fork] hugo port for 9front
(HTM) git clone https://git.drkhsh.at/hugo.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Submodules
(DIR) README
(DIR) LICENSE
---
url.go (6563B)
---
1 // Copyright 2024 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 "runtime"
22 "strings"
23 )
24
25 type pathBridge struct{}
26
27 func (pathBridge) Base(in string) string {
28 return path.Base(in)
29 }
30
31 func (pathBridge) Clean(in string) string {
32 return path.Clean(in)
33 }
34
35 func (pathBridge) Dir(in string) string {
36 return path.Dir(in)
37 }
38
39 func (pathBridge) Ext(in string) string {
40 return path.Ext(in)
41 }
42
43 func (pathBridge) Join(elem ...string) string {
44 return path.Join(elem...)
45 }
46
47 func (pathBridge) Separator() string {
48 return "/"
49 }
50
51 var pb pathBridge
52
53 // MakePermalink combines base URL with content path to create full URL paths.
54 // Example
55 //
56 // base: http://spf13.com/
57 // path: post/how-i-blog
58 // result: http://spf13.com/post/how-i-blog
59 func MakePermalink(host, plink string) *url.URL {
60 base, err := url.Parse(host)
61 if err != nil {
62 panic(err)
63 }
64
65 p, err := url.Parse(plink)
66 if err != nil {
67 panic(err)
68 }
69
70 if p.Host != "" {
71 panic(fmt.Errorf("can't make permalink from absolute link %q", plink))
72 }
73
74 base.Path = path.Join(base.Path, p.Path)
75 base.Fragment = p.Fragment
76 base.RawQuery = p.RawQuery
77
78 // path.Join will strip off the last /, so put it back if it was there.
79 hadTrailingSlash := (plink == "" && strings.HasSuffix(host, "/")) || strings.HasSuffix(p.Path, "/")
80 if hadTrailingSlash && !strings.HasSuffix(base.Path, "/") {
81 base.Path = base.Path + "/"
82 }
83
84 return base
85 }
86
87 // AddContextRoot adds the context root to an URL if it's not already set.
88 // For relative URL entries on sites with a base url with a context root set (i.e. http://example.com/mysite),
89 // relative URLs must not include the context root if canonifyURLs is enabled. But if it's disabled, it must be set.
90 func AddContextRoot(baseURL, relativePath string) string {
91 url, err := url.Parse(baseURL)
92 if err != nil {
93 panic(err)
94 }
95
96 newPath := path.Join(url.Path, relativePath)
97
98 // path strips trailing slash, ignore root path.
99 if newPath != "/" && strings.HasSuffix(relativePath, "/") {
100 newPath += "/"
101 }
102 return newPath
103 }
104
105 // URLizeAn
106
107 // PrettifyURL takes a URL string and returns a semantic, clean URL.
108 func PrettifyURL(in string) string {
109 x := PrettifyURLPath(in)
110
111 if path.Base(x) == "index.html" {
112 return path.Dir(x)
113 }
114
115 if in == "" {
116 return "/"
117 }
118
119 return x
120 }
121
122 // PrettifyURLPath takes a URL path to a content and converts it
123 // to enable pretty URLs.
124 //
125 // /section/name.html becomes /section/name/index.html
126 // /section/name/ becomes /section/name/index.html
127 // /section/name/index.html becomes /section/name/index.html
128 func PrettifyURLPath(in string) string {
129 return prettifyPath(in, pb)
130 }
131
132 // Uglify does the opposite of PrettifyURLPath().
133 //
134 // /section/name/index.html becomes /section/name.html
135 // /section/name/ becomes /section/name.html
136 // /section/name.html becomes /section/name.html
137 func Uglify(in string) string {
138 if path.Ext(in) == "" {
139 if len(in) < 2 {
140 return "/"
141 }
142 // /section/name/ -> /section/name.html
143 return path.Clean(in) + ".html"
144 }
145
146 name, ext := fileAndExt(in, pb)
147 if name == "index" {
148 // /section/name/index.html -> /section/name.html
149 d := path.Dir(in)
150 if len(d) > 1 {
151 return d + ext
152 }
153 return in
154 }
155 // /.xml -> /index.xml
156 if name == "" {
157 return path.Dir(in) + "index" + ext
158 }
159 // /section/name.html -> /section/name.html
160 return path.Clean(in)
161 }
162
163 // URLEscape escapes unicode letters.
164 func URLEscape(uri string) string {
165 // escape unicode letters
166 u, err := url.Parse(uri)
167 if err != nil {
168 panic(err)
169 }
170 return u.String()
171 }
172
173 // TrimExt trims the extension from a path..
174 func TrimExt(in string) string {
175 return strings.TrimSuffix(in, path.Ext(in))
176 }
177
178 // From https://github.com/golang/go/blob/e0c76d95abfc1621259864adb3d101cf6f1f90fc/src/cmd/go/internal/web/url.go#L45
179 func UrlFromFilename(filename string) (*url.URL, error) {
180 if !filepath.IsAbs(filename) {
181 return nil, fmt.Errorf("filepath must be absolute")
182 }
183
184 // If filename has a Windows volume name, convert the volume to a host and prefix
185 // per https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/.
186 if vol := filepath.VolumeName(filename); vol != "" {
187 if strings.HasPrefix(vol, `\\`) {
188 filename = filepath.ToSlash(filename[2:])
189 i := strings.IndexByte(filename, '/')
190
191 if i < 0 {
192 // A degenerate case.
193 // \\host.example.com (without a share name)
194 // becomes
195 // file://host.example.com/
196 return &url.URL{
197 Scheme: "file",
198 Host: filename,
199 Path: "/",
200 }, nil
201 }
202
203 // \\host.example.com\Share\path\to\file
204 // becomes
205 // file://host.example.com/Share/path/to/file
206 return &url.URL{
207 Scheme: "file",
208 Host: filename[:i],
209 Path: filepath.ToSlash(filename[i:]),
210 }, nil
211 }
212
213 // C:\path\to\file
214 // becomes
215 // file:///C:/path/to/file
216 return &url.URL{
217 Scheme: "file",
218 Path: "/" + filepath.ToSlash(filename),
219 }, nil
220 }
221
222 // /path/to/file
223 // becomes
224 // file:///path/to/file
225 return &url.URL{
226 Scheme: "file",
227 Path: filepath.ToSlash(filename),
228 }, nil
229 }
230
231 // UrlStringToFilename converts the URL s to a filename.
232 // If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
233 func UrlStringToFilename(s string) (string, bool) {
234 u, err := url.ParseRequestURI(s)
235 if err != nil {
236 return filepath.FromSlash(s), false
237 }
238
239 p := u.Path
240
241 if p == "" {
242 p, _ = url.QueryUnescape(u.Opaque)
243 return filepath.FromSlash(p), false
244 }
245
246 if runtime.GOOS != "windows" {
247 return p, true
248 }
249
250 if len(p) == 0 || p[0] != '/' {
251 return filepath.FromSlash(p), false
252 }
253
254 p = filepath.FromSlash(p)
255
256 if len(u.Host) == 1 {
257 // file://c/Users/...
258 return strings.ToUpper(u.Host) + ":" + p, true
259 }
260
261 if u.Host != "" && u.Host != "localhost" {
262 if filepath.VolumeName(u.Host) != "" {
263 return "", false
264 }
265 return `\\` + u.Host + p, true
266 }
267
268 if vol := filepath.VolumeName(p[1:]); vol == "" || strings.HasPrefix(vol, `\\`) {
269 return "", false
270 }
271
272 return p[1:], true
273 }