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 }