github.com/minio/madmin-go/v3@v3.0.51/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  	"net"
    25  	"net/http"
    26  	"net/url"
    27  	"strings"
    28  
    29  	"github.com/minio/minio-go/v7/pkg/s3utils"
    30  )
    31  
    32  // AdminAPIVersion - admin api version used in the request.
    33  const (
    34  	AdminAPIVersion   = "v3"
    35  	AdminAPIVersionV2 = "v2"
    36  	adminAPIPrefix    = "/" + AdminAPIVersion
    37  	kmsAPIVersion     = "v1"
    38  	kmsAPIPrefix      = "/" + kmsAPIVersion
    39  )
    40  
    41  // getEndpointURL - construct a new endpoint.
    42  func getEndpointURL(endpoint string, secure bool) (*url.URL, error) {
    43  	if strings.Contains(endpoint, ":") {
    44  		host, _, err := net.SplitHostPort(endpoint)
    45  		if err != nil {
    46  			return nil, err
    47  		}
    48  		if !s3utils.IsValidIP(host) && !s3utils.IsValidDomain(host) {
    49  			msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
    50  			return nil, ErrInvalidArgument(msg)
    51  		}
    52  	} else {
    53  		if !s3utils.IsValidIP(endpoint) && !s3utils.IsValidDomain(endpoint) {
    54  			msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
    55  			return nil, ErrInvalidArgument(msg)
    56  		}
    57  	}
    58  
    59  	// If secure is false, use 'http' scheme.
    60  	scheme := "https"
    61  	if !secure {
    62  		scheme = "http"
    63  	}
    64  
    65  	// Strip the obvious :443 and :80 from the endpoint
    66  	// to avoid the signature mismatch error.
    67  	if secure && strings.HasSuffix(endpoint, ":443") {
    68  		endpoint = strings.TrimSuffix(endpoint, ":443")
    69  	}
    70  	if !secure && strings.HasSuffix(endpoint, ":80") {
    71  		endpoint = strings.TrimSuffix(endpoint, ":80")
    72  	}
    73  
    74  	// Construct a secured endpoint URL.
    75  	endpointURLStr := scheme + "://" + endpoint
    76  	endpointURL, err := url.Parse(endpointURLStr)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	// Validate incoming endpoint URL.
    82  	if err := isValidEndpointURL(endpointURL.String()); err != nil {
    83  		return nil, err
    84  	}
    85  	return endpointURL, nil
    86  }
    87  
    88  // Verify if input endpoint URL is valid.
    89  func isValidEndpointURL(endpointURL string) error {
    90  	if endpointURL == "" {
    91  		return ErrInvalidArgument("Endpoint url cannot be empty.")
    92  	}
    93  	url, err := url.Parse(endpointURL)
    94  	if err != nil {
    95  		return ErrInvalidArgument("Endpoint url cannot be parsed.")
    96  	}
    97  	if url.Path != "/" && url.Path != "" {
    98  		return ErrInvalidArgument("Endpoint url cannot have fully qualified paths.")
    99  	}
   100  	return nil
   101  }
   102  
   103  // closeResponse close non nil response with any response Body.
   104  // convenient wrapper to drain any remaining data on response body.
   105  //
   106  // Subsequently this allows golang http RoundTripper
   107  // to re-use the same connection for future requests.
   108  func closeResponse(resp *http.Response) {
   109  	// Callers should close resp.Body when done reading from it.
   110  	// If resp.Body is not closed, the Client's underlying RoundTripper
   111  	// (typically Transport) may not be able to re-use a persistent TCP
   112  	// connection to the server for a subsequent "keep-alive" request.
   113  	if resp != nil && resp.Body != nil {
   114  		// Drain any remaining Body and then close the connection.
   115  		// Without this closing connection would disallow re-using
   116  		// the same connection for future uses.
   117  		//  - http://stackoverflow.com/a/17961593/4465767
   118  		io.Copy(io.Discard, resp.Body)
   119  		resp.Body.Close()
   120  	}
   121  }