github.com/minio/madmin-go/v2@v2.2.1/utils.go (about)

     1  //
     2  // Copyright (c) 2015-2022 MinIO, Inc.
     3  //
     4  // This file is part of MinIO Object Storage stack
     5  //
     6  // This program is free software: you can redistribute it and/or modify
     7  // it under the terms of the GNU Affero General Public License as
     8  // published by the Free Software Foundation, either version 3 of the
     9  // License, or (at your option) any later version.
    10  //
    11  // This program is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  // GNU Affero General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU Affero General Public License
    17  // along with this program. If not, see <http://www.gnu.org/licenses/>.
    18  //
    19  
    20  package madmin
    21  
    22  import (
    23  	"io"
    24  	"io/ioutil"
    25  	"net"
    26  	"net/http"
    27  	"net/url"
    28  	"strings"
    29  
    30  	"github.com/minio/minio-go/v7/pkg/s3utils"
    31  )
    32  
    33  // AdminAPIVersion - admin api version used in the request.
    34  const (
    35  	AdminAPIVersion   = "v3"
    36  	AdminAPIVersionV2 = "v2"
    37  	adminAPIPrefix    = "/" + AdminAPIVersion
    38  	kmsAPIVersion     = "v1"
    39  	kmsAPIPrefix      = "/" + kmsAPIVersion
    40  )
    41  
    42  // getEndpointURL - construct a new endpoint.
    43  func getEndpointURL(endpoint string, secure bool) (*url.URL, error) {
    44  	if strings.Contains(endpoint, ":") {
    45  		host, _, err := net.SplitHostPort(endpoint)
    46  		if err != nil {
    47  			return nil, err
    48  		}
    49  		if !s3utils.IsValidIP(host) && !s3utils.IsValidDomain(host) {
    50  			msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
    51  			return nil, ErrInvalidArgument(msg)
    52  		}
    53  	} else {
    54  		if !s3utils.IsValidIP(endpoint) && !s3utils.IsValidDomain(endpoint) {
    55  			msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
    56  			return nil, ErrInvalidArgument(msg)
    57  		}
    58  	}
    59  
    60  	// If secure is false, use 'http' scheme.
    61  	scheme := "https"
    62  	if !secure {
    63  		scheme = "http"
    64  	}
    65  
    66  	// Strip the obvious :443 and :80 from the endpoint
    67  	// to avoid the signature mismatch error.
    68  	if secure && strings.HasSuffix(endpoint, ":443") {
    69  		endpoint = strings.TrimSuffix(endpoint, ":443")
    70  	}
    71  	if !secure && strings.HasSuffix(endpoint, ":80") {
    72  		endpoint = strings.TrimSuffix(endpoint, ":80")
    73  	}
    74  
    75  	// Construct a secured endpoint URL.
    76  	endpointURLStr := scheme + "://" + endpoint
    77  	endpointURL, err := url.Parse(endpointURLStr)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	// Validate incoming endpoint URL.
    83  	if err := isValidEndpointURL(endpointURL.String()); err != nil {
    84  		return nil, err
    85  	}
    86  	return endpointURL, nil
    87  }
    88  
    89  // Verify if input endpoint URL is valid.
    90  func isValidEndpointURL(endpointURL string) error {
    91  	if endpointURL == "" {
    92  		return ErrInvalidArgument("Endpoint url cannot be empty.")
    93  	}
    94  	url, err := url.Parse(endpointURL)
    95  	if err != nil {
    96  		return ErrInvalidArgument("Endpoint url cannot be parsed.")
    97  	}
    98  	if url.Path != "/" && url.Path != "" {
    99  		return ErrInvalidArgument("Endpoint url cannot have fully qualified paths.")
   100  	}
   101  	return nil
   102  }
   103  
   104  // closeResponse close non nil response with any response Body.
   105  // convenient wrapper to drain any remaining data on response body.
   106  //
   107  // Subsequently this allows golang http RoundTripper
   108  // to re-use the same connection for future requests.
   109  func closeResponse(resp *http.Response) {
   110  	// Callers should close resp.Body when done reading from it.
   111  	// If resp.Body is not closed, the Client's underlying RoundTripper
   112  	// (typically Transport) may not be able to re-use a persistent TCP
   113  	// connection to the server for a subsequent "keep-alive" request.
   114  	if resp != nil && resp.Body != nil {
   115  		// Drain any remaining Body and then close the connection.
   116  		// Without this closing connection would disallow re-using
   117  		// the same connection for future uses.
   118  		//  - http://stackoverflow.com/a/17961593/4465767
   119  		io.Copy(ioutil.Discard, resp.Body)
   120  		resp.Body.Close()
   121  	}
   122  }