github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/common/paths/url.go (about) 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 "strings" 21 22 "github.com/PuerkitoBio/purell" 23 ) 24 25 type pathBridge struct { 26 } 27 28 func (pathBridge) Base(in string) string { 29 return path.Base(in) 30 } 31 32 func (pathBridge) Clean(in string) string { 33 return path.Clean(in) 34 } 35 36 func (pathBridge) Dir(in string) string { 37 return path.Dir(in) 38 } 39 40 func (pathBridge) Ext(in string) string { 41 return path.Ext(in) 42 } 43 44 func (pathBridge) Join(elem ...string) string { 45 return path.Join(elem...) 46 } 47 48 func (pathBridge) Separator() string { 49 return "/" 50 } 51 52 var pb pathBridge 53 54 func sanitizeURLWithFlags(in string, f purell.NormalizationFlags) string { 55 s, err := purell.NormalizeURLString(in, f) 56 if err != nil { 57 return in 58 } 59 60 // Temporary workaround for the bug fix and resulting 61 // behavioral change in purell.NormalizeURLString(): 62 // a leading '/' was inadvertently added to relative links, 63 // but no longer, see #878. 64 // 65 // I think the real solution is to allow Hugo to 66 // make relative URL with relative path, 67 // e.g. "../../post/hello-again/", as wished by users 68 // in issues #157, #622, etc., without forcing 69 // relative URLs to begin with '/'. 70 // Once the fixes are in, let's remove this kludge 71 // and restore SanitizeURL() to the way it was. 72 // -- @anthonyfok, 2015-02-16 73 // 74 // Begin temporary kludge 75 u, err := url.Parse(s) 76 if err != nil { 77 panic(err) 78 } 79 if len(u.Path) > 0 && !strings.HasPrefix(u.Path, "/") { 80 u.Path = "/" + u.Path 81 } 82 return u.String() 83 // End temporary kludge 84 85 // return s 86 87 } 88 89 // SanitizeURL sanitizes the input URL string. 90 func SanitizeURL(in string) string { 91 return sanitizeURLWithFlags(in, purell.FlagsSafe|purell.FlagRemoveTrailingSlash|purell.FlagRemoveDotSegments|purell.FlagRemoveDuplicateSlashes|purell.FlagRemoveUnnecessaryHostDots|purell.FlagRemoveEmptyPortSeparator) 92 } 93 94 // SanitizeURLKeepTrailingSlash is the same as SanitizeURL, but will keep any trailing slash. 95 func SanitizeURLKeepTrailingSlash(in string) string { 96 return sanitizeURLWithFlags(in, purell.FlagsSafe|purell.FlagRemoveDotSegments|purell.FlagRemoveDuplicateSlashes|purell.FlagRemoveUnnecessaryHostDots|purell.FlagRemoveEmptyPortSeparator) 97 } 98 99 // MakePermalink combines base URL with content path to create full URL paths. 100 // Example 101 // base: http://spf13.com/ 102 // path: post/how-i-blog 103 // result: http://spf13.com/post/how-i-blog 104 func MakePermalink(host, plink string) *url.URL { 105 base, err := url.Parse(host) 106 if err != nil { 107 panic(err) 108 } 109 110 p, err := url.Parse(plink) 111 if err != nil { 112 panic(err) 113 } 114 115 if p.Host != "" { 116 panic(fmt.Errorf("can't make permalink from absolute link %q", plink)) 117 } 118 119 base.Path = path.Join(base.Path, p.Path) 120 121 // path.Join will strip off the last /, so put it back if it was there. 122 hadTrailingSlash := (plink == "" && strings.HasSuffix(host, "/")) || strings.HasSuffix(p.Path, "/") 123 if hadTrailingSlash && !strings.HasSuffix(base.Path, "/") { 124 base.Path = base.Path + "/" 125 } 126 127 return base 128 } 129 130 // IsAbsURL determines whether the given path points to an absolute URL. 131 func IsAbsURL(path string) bool { 132 url, err := url.Parse(path) 133 if err != nil { 134 return false 135 } 136 137 return url.IsAbs() || strings.HasPrefix(path, "//") 138 } 139 140 // AddContextRoot adds the context root to an URL if it's not already set. 141 // For relative URL entries on sites with a base url with a context root set (i.e. http://example.com/mysite), 142 // relative URLs must not include the context root if canonifyURLs is enabled. But if it's disabled, it must be set. 143 func AddContextRoot(baseURL, relativePath string) string { 144 url, err := url.Parse(baseURL) 145 if err != nil { 146 panic(err) 147 } 148 149 newPath := path.Join(url.Path, relativePath) 150 151 // path strips trailing slash, ignore root path. 152 if newPath != "/" && strings.HasSuffix(relativePath, "/") { 153 newPath += "/" 154 } 155 return newPath 156 } 157 158 // URLizeAn 159 160 // PrettifyURL takes a URL string and returns a semantic, clean URL. 161 func PrettifyURL(in string) string { 162 x := PrettifyURLPath(in) 163 164 if path.Base(x) == "index.html" { 165 return path.Dir(x) 166 } 167 168 if in == "" { 169 return "/" 170 } 171 172 return x 173 } 174 175 // PrettifyURLPath takes a URL path to a content and converts it 176 // to enable pretty URLs. 177 // /section/name.html becomes /section/name/index.html 178 // /section/name/ becomes /section/name/index.html 179 // /section/name/index.html becomes /section/name/index.html 180 func PrettifyURLPath(in string) string { 181 return prettifyPath(in, pb) 182 } 183 184 // Uglify does the opposite of PrettifyURLPath(). 185 // /section/name/index.html becomes /section/name.html 186 // /section/name/ becomes /section/name.html 187 // /section/name.html becomes /section/name.html 188 func Uglify(in string) string { 189 if path.Ext(in) == "" { 190 if len(in) < 2 { 191 return "/" 192 } 193 // /section/name/ -> /section/name.html 194 return path.Clean(in) + ".html" 195 } 196 197 name, ext := fileAndExt(in, pb) 198 if name == "index" { 199 // /section/name/index.html -> /section/name.html 200 d := path.Dir(in) 201 if len(d) > 1 { 202 return d + ext 203 } 204 return in 205 } 206 // /.xml -> /index.xml 207 if name == "" { 208 return path.Dir(in) + "index" + ext 209 } 210 // /section/name.html -> /section/name.html 211 return path.Clean(in) 212 }