github.com/minio/console@v1.4.1/api/public_objects.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2024 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	b64 "encoding/base64"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  	"net/url"
    25  	"strings"
    26  
    27  	"github.com/go-openapi/runtime"
    28  	"github.com/go-openapi/runtime/middleware"
    29  	"github.com/go-openapi/swag"
    30  	"github.com/minio/console/api/operations"
    31  	"github.com/minio/console/api/operations/public"
    32  	xnet "github.com/minio/pkg/v3/net"
    33  )
    34  
    35  func registerPublicObjectsHandlers(api *operations.ConsoleAPI) {
    36  	api.PublicDownloadSharedObjectHandler = public.DownloadSharedObjectHandlerFunc(func(params public.DownloadSharedObjectParams) middleware.Responder {
    37  		resp, err := getDownloadPublicObjectResponse(params)
    38  		if err != nil {
    39  			return public.NewDownloadSharedObjectDefault(err.Code).WithPayload(err.APIError)
    40  		}
    41  		return resp
    42  	})
    43  }
    44  
    45  func getDownloadPublicObjectResponse(params public.DownloadSharedObjectParams) (middleware.Responder, *CodedAPIError) {
    46  	ctx := params.HTTPRequest.Context()
    47  
    48  	inputURLDecoded, err := b64toMinIOStringURL(params.URL)
    49  	if err != nil {
    50  		return nil, ErrorWithContext(ctx, err)
    51  	}
    52  	if inputURLDecoded == nil {
    53  		return nil, ErrorWithContext(ctx, ErrDefault, fmt.Errorf("decoded url is null"))
    54  	}
    55  
    56  	req, err := http.NewRequest(http.MethodGet, *inputURLDecoded, nil)
    57  	if err != nil {
    58  		return nil, ErrorWithContext(ctx, err)
    59  	}
    60  
    61  	clnt := PrepareConsoleHTTPClient(getClientIP(params.HTTPRequest))
    62  	resp, err := clnt.Do(req)
    63  	if err != nil {
    64  		return nil, ErrorWithContext(ctx, err)
    65  	}
    66  
    67  	return middleware.ResponderFunc(func(rw http.ResponseWriter, _ runtime.Producer) {
    68  		defer resp.Body.Close()
    69  
    70  		if resp.StatusCode != http.StatusOK {
    71  			http.Error(rw, resp.Status, resp.StatusCode)
    72  			return
    73  		}
    74  
    75  		urlObj, err := url.Parse(*inputURLDecoded)
    76  		if err != nil {
    77  			http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
    78  			return
    79  		}
    80  
    81  		// Add the filename
    82  		_, objectName := url2BucketAndObject(urlObj)
    83  		escapedName := url.PathEscape(objectName)
    84  		rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", escapedName))
    85  
    86  		_, err = io.Copy(rw, resp.Body)
    87  		if err != nil {
    88  			http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
    89  			return
    90  		}
    91  	}), nil
    92  }
    93  
    94  // b64toMinIOStringURL decodes url and validates is a MinIO url endpoint
    95  func b64toMinIOStringURL(inputEncodedURL string) (*string, error) {
    96  	inputURLDecoded, err := b64.URLEncoding.DecodeString(inputEncodedURL)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	// Validate input URL
   101  	inputURL, err := xnet.ParseHTTPURL(string(inputURLDecoded))
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	// Ensure incoming url points to MinIO Server
   106  	minIOHost := getMinIOEndpoint()
   107  	if inputURL.Host != minIOHost {
   108  		return nil, ErrForbidden
   109  	}
   110  	return swag.String(string(inputURLDecoded)), nil
   111  }
   112  
   113  func url2BucketAndObject(u *url.URL) (bucketName, objectName string) {
   114  	tokens := splitStr(u.Path, "/", 3)
   115  	return tokens[1], tokens[2]
   116  }
   117  
   118  // splitStr splits a string into n parts, empty strings are added
   119  // if we are not able to reach n elements
   120  func splitStr(path, sep string, n int) []string {
   121  	splits := strings.SplitN(path, sep, n)
   122  	// Add empty strings if we found elements less than nr
   123  	for i := n - len(splits); i > 0; i-- {
   124  		splits = append(splits, "")
   125  	}
   126  	return splits
   127  }