storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/net/url.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package net 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "net" 25 "net/url" 26 "path" 27 "strings" 28 ) 29 30 // URL - improved JSON friendly url.URL. 31 type URL url.URL 32 33 // IsEmpty - checks URL is empty or not. 34 func (u URL) IsEmpty() bool { 35 return u.String() == "" 36 } 37 38 // String - returns string representation of URL. 39 func (u URL) String() string { 40 // if port number 80 and 443, remove for http and https scheme respectively 41 if u.Host != "" { 42 host, err := ParseHost(u.Host) 43 if err != nil { 44 panic(err) 45 } 46 switch { 47 case u.Scheme == "http" && host.Port == 80: 48 fallthrough 49 case u.Scheme == "https" && host.Port == 443: 50 u.Host = host.Name 51 } 52 } 53 54 uu := url.URL(u) 55 return uu.String() 56 } 57 58 // MarshalJSON - converts to JSON string data. 59 func (u URL) MarshalJSON() ([]byte, error) { 60 return json.Marshal(u.String()) 61 } 62 63 // UnmarshalJSON - parses given data into URL. 64 func (u *URL) UnmarshalJSON(data []byte) (err error) { 65 var s string 66 if err = json.Unmarshal(data, &s); err != nil { 67 return err 68 } 69 70 // Allow empty string 71 if s == "" { 72 *u = URL{} 73 return nil 74 } 75 76 var ru *URL 77 if ru, err = ParseURL(s); err != nil { 78 return err 79 } 80 81 *u = *ru 82 return nil 83 } 84 85 // ParseHTTPURL - parses a string into HTTP URL, string is 86 // expected to be of form http:// or https:// 87 func ParseHTTPURL(s string) (u *URL, err error) { 88 u, err = ParseURL(s) 89 if err != nil { 90 return nil, err 91 } 92 switch u.Scheme { 93 default: 94 return nil, fmt.Errorf("unexpected scheme found %s", u.Scheme) 95 case "http", "https": 96 return u, nil 97 } 98 } 99 100 // ParseURL - parses string into URL. 101 func ParseURL(s string) (u *URL, err error) { 102 var uu *url.URL 103 if uu, err = url.Parse(s); err != nil { 104 return nil, err 105 } 106 107 if uu.Hostname() == "" { 108 if uu.Scheme != "" { 109 return nil, errors.New("scheme appears with empty host") 110 } 111 } else { 112 portStr := uu.Port() 113 if portStr == "" { 114 switch uu.Scheme { 115 case "http": 116 portStr = "80" 117 case "https": 118 portStr = "443" 119 } 120 } 121 if _, err = ParseHost(net.JoinHostPort(uu.Hostname(), portStr)); err != nil { 122 return nil, err 123 } 124 } 125 126 // Clean path in the URL. 127 // Note: path.Clean() is used on purpose because in MS Windows filepath.Clean() converts 128 // `/` into `\` ie `/foo` becomes `\foo` 129 if uu.Path != "" { 130 uu.Path = path.Clean(uu.Path) 131 } 132 133 // path.Clean removes the trailing '/' and converts '//' to '/'. 134 if strings.HasSuffix(s, "/") && !strings.HasSuffix(uu.Path, "/") { 135 uu.Path += "/" 136 } 137 138 v := URL(*uu) 139 u = &v 140 return u, nil 141 } 142 143 // IsNetworkOrHostDown - if there was a network error or if the host is down. 144 // expectTimeouts indicates that *context* timeouts are expected and does not 145 // indicate a downed host. Other timeouts still returns down. 146 func IsNetworkOrHostDown(err error, expectTimeouts bool) bool { 147 if err == nil { 148 return false 149 } 150 151 if errors.Is(err, context.Canceled) { 152 return false 153 } 154 155 if expectTimeouts && errors.Is(err, context.DeadlineExceeded) { 156 return false 157 } 158 159 // We need to figure if the error either a timeout 160 // or a non-temporary error. 161 urlErr := &url.Error{} 162 if errors.As(err, &urlErr) { 163 switch urlErr.Err.(type) { 164 case *net.DNSError, *net.OpError, net.UnknownNetworkError: 165 return true 166 } 167 } 168 169 var e net.Error 170 if errors.As(err, &e) { 171 if e.Timeout() { 172 return true 173 } 174 } 175 176 // Fallback to other mechanisms. 177 switch { 178 case strings.Contains(err.Error(), "Connection closed by foreign host"): 179 return true 180 case strings.Contains(err.Error(), "TLS handshake timeout"): 181 // If error is - tlsHandshakeTimeoutError. 182 return true 183 case strings.Contains(err.Error(), "i/o timeout"): 184 // If error is - tcp timeoutError. 185 return true 186 case strings.Contains(err.Error(), "connection timed out"): 187 // If err is a net.Dial timeout. 188 return true 189 case strings.Contains(err.Error(), "connection reset by peer"): 190 // IF err is a peer reset on a socket. 191 return true 192 case strings.Contains(err.Error(), "broken pipe"): 193 // IF err is a broken pipe on a socket. 194 return true 195 case strings.Contains(strings.ToLower(err.Error()), "503 service unavailable"): 196 // Denial errors 197 return true 198 } 199 return false 200 }