github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/helpers/url.go (about) 1 // Copyright 2015 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 helpers 15 16 import ( 17 "net/url" 18 "path" 19 "path/filepath" 20 "strings" 21 22 "github.com/gohugoio/hugo/common/paths" 23 24 "github.com/PuerkitoBio/purell" 25 ) 26 27 func sanitizeURLWithFlags(in string, f purell.NormalizationFlags) string { 28 s, err := purell.NormalizeURLString(in, f) 29 if err != nil { 30 return in 31 } 32 33 // Temporary workaround for the bug fix and resulting 34 // behavioral change in purell.NormalizeURLString(): 35 // a leading '/' was inadvertently added to relative links, 36 // but no longer, see #878. 37 // 38 // I think the real solution is to allow Hugo to 39 // make relative URL with relative path, 40 // e.g. "../../post/hello-again/", as wished by users 41 // in issues #157, #622, etc., without forcing 42 // relative URLs to begin with '/'. 43 // Once the fixes are in, let's remove this kludge 44 // and restore SanitizeURL() to the way it was. 45 // -- @anthonyfok, 2015-02-16 46 // 47 // Begin temporary kludge 48 u, err := url.Parse(s) 49 if err != nil { 50 panic(err) 51 } 52 if len(u.Path) > 0 && !strings.HasPrefix(u.Path, "/") { 53 u.Path = "/" + u.Path 54 } 55 return u.String() 56 // End temporary kludge 57 58 // return s 59 60 } 61 62 // SanitizeURL sanitizes the input URL string. 63 func SanitizeURL(in string) string { 64 return sanitizeURLWithFlags(in, purell.FlagsSafe|purell.FlagRemoveTrailingSlash|purell.FlagRemoveDotSegments|purell.FlagRemoveDuplicateSlashes|purell.FlagRemoveUnnecessaryHostDots|purell.FlagRemoveEmptyPortSeparator) 65 } 66 67 // SanitizeURLKeepTrailingSlash is the same as SanitizeURL, but will keep any trailing slash. 68 func SanitizeURLKeepTrailingSlash(in string) string { 69 return sanitizeURLWithFlags(in, purell.FlagsSafe|purell.FlagRemoveDotSegments|purell.FlagRemoveDuplicateSlashes|purell.FlagRemoveUnnecessaryHostDots|purell.FlagRemoveEmptyPortSeparator) 70 } 71 72 // URLize is similar to MakePath, but with Unicode handling 73 // Example: 74 // uri: Vim (text editor) 75 // urlize: vim-text-editor 76 func (p *PathSpec) URLize(uri string) string { 77 return p.URLEscape(p.MakePathSanitized(uri)) 78 } 79 80 // URLizeFilename creates an URL from a filename by escaping unicode letters 81 // and turn any filepath separator into forward slashes. 82 func (p *PathSpec) URLizeFilename(filename string) string { 83 return p.URLEscape(filepath.ToSlash(filename)) 84 } 85 86 // URLEscape escapes unicode letters. 87 func (p *PathSpec) URLEscape(uri string) string { 88 // escape unicode letters 89 parsedURI, err := url.Parse(uri) 90 if err != nil { 91 // if net/url can not parse URL it means Sanitize works incorrectly 92 panic(err) 93 } 94 x := parsedURI.String() 95 return x 96 } 97 98 // AbsURL creates an absolute URL from the relative path given and the BaseURL set in config. 99 func (p *PathSpec) AbsURL(in string, addLanguage bool) string { 100 url, err := url.Parse(in) 101 if err != nil { 102 return in 103 } 104 105 if url.IsAbs() || strings.HasPrefix(in, "//") { 106 return in 107 } 108 109 var baseURL string 110 if strings.HasPrefix(in, "/") { 111 u := p.BaseURL.URL() 112 u.Path = "" 113 baseURL = u.String() 114 } else { 115 baseURL = p.BaseURL.String() 116 } 117 118 if addLanguage { 119 prefix := p.GetLanguagePrefix() 120 if prefix != "" { 121 hasPrefix := false 122 // avoid adding language prefix if already present 123 in2 := in 124 if strings.HasPrefix(in, "/") { 125 in2 = in[1:] 126 } 127 if in2 == prefix { 128 hasPrefix = true 129 } else { 130 hasPrefix = strings.HasPrefix(in2, prefix+"/") 131 } 132 133 if !hasPrefix { 134 addSlash := in == "" || strings.HasSuffix(in, "/") 135 in = path.Join(prefix, in) 136 137 if addSlash { 138 in += "/" 139 } 140 } 141 } 142 } 143 return paths.MakePermalink(baseURL, in).String() 144 } 145 146 // RelURL creates a URL relative to the BaseURL root. 147 // Note: The result URL will not include the context root if canonifyURLs is enabled. 148 func (p *PathSpec) RelURL(in string, addLanguage bool) string { 149 baseURL := p.BaseURL.String() 150 canonifyURLs := p.CanonifyURLs 151 if (!strings.HasPrefix(in, baseURL) && strings.HasPrefix(in, "http")) || strings.HasPrefix(in, "//") { 152 return in 153 } 154 155 u := in 156 157 if strings.HasPrefix(in, baseURL) { 158 u = strings.TrimPrefix(u, baseURL) 159 } 160 161 if addLanguage { 162 prefix := p.GetLanguagePrefix() 163 if prefix != "" { 164 hasPrefix := false 165 // avoid adding language prefix if already present 166 in2 := in 167 if strings.HasPrefix(in, "/") { 168 in2 = in[1:] 169 } 170 if in2 == prefix { 171 hasPrefix = true 172 } else { 173 hasPrefix = strings.HasPrefix(in2, prefix+"/") 174 } 175 176 if !hasPrefix { 177 hadSlash := strings.HasSuffix(u, "/") 178 179 u = path.Join(prefix, u) 180 181 if hadSlash { 182 u += "/" 183 } 184 } 185 } 186 } 187 188 if !canonifyURLs { 189 u = paths.AddContextRoot(baseURL, u) 190 } 191 192 if in == "" && !strings.HasSuffix(u, "/") && strings.HasSuffix(baseURL, "/") { 193 u += "/" 194 } 195 196 if !strings.HasPrefix(u, "/") { 197 u = "/" + u 198 } 199 200 return u 201 } 202 203 // PrependBasePath prepends any baseURL sub-folder to the given resource 204 func (p *PathSpec) PrependBasePath(rel string, isAbs bool) string { 205 basePath := p.GetBasePath(!isAbs) 206 if basePath != "" { 207 rel = filepath.ToSlash(rel) 208 // Need to prepend any path from the baseURL 209 hadSlash := strings.HasSuffix(rel, "/") 210 rel = path.Join(basePath, rel) 211 if hadSlash { 212 rel += "/" 213 } 214 } 215 return rel 216 } 217 218 // URLizeAndPrep applies misc sanitation to the given URL to get it in line 219 // with the Hugo standard. 220 func (p *PathSpec) URLizeAndPrep(in string) string { 221 return p.URLPrep(p.URLize(in)) 222 } 223 224 // URLPrep applies misc sanitation to the given URL. 225 func (p *PathSpec) URLPrep(in string) string { 226 if p.UglyURLs { 227 return paths.Uglify(SanitizeURL(in)) 228 } 229 pretty := paths.PrettifyURL(SanitizeURL(in)) 230 if path.Ext(pretty) == ".xml" { 231 return pretty 232 } 233 url, err := purell.NormalizeURLString(pretty, purell.FlagAddTrailingSlash) 234 if err != nil { 235 return pretty 236 } 237 return url 238 }