github.com/minio/console@v1.3.0/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/v2/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  	resp, err := http.DefaultClient.Do(req)
    61  	if err != nil {
    62  		return nil, ErrorWithContext(ctx, err)
    63  	}
    64  
    65  	return middleware.ResponderFunc(func(rw http.ResponseWriter, _ runtime.Producer) {
    66  		defer resp.Body.Close()
    67  
    68  		if resp.StatusCode != http.StatusOK {
    69  			http.Error(rw, resp.Status, resp.StatusCode)
    70  			return
    71  		}
    72  
    73  		urlObj, err := url.Parse(*inputURLDecoded)
    74  		if err != nil {
    75  			http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
    76  			return
    77  		}
    78  
    79  		// Add the filename
    80  		_, objectName := url2BucketAndObject(urlObj)
    81  		escapedName := url.PathEscape(objectName)
    82  		rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", escapedName))
    83  
    84  		_, err = io.Copy(rw, resp.Body)
    85  		if err != nil {
    86  			http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
    87  			return
    88  		}
    89  	}), nil
    90  }
    91  
    92  // b64toMinIOStringURL decodes url and validates is a MinIO url endpoint
    93  func b64toMinIOStringURL(inputEncodedURL string) (*string, error) {
    94  	inputURLDecoded, err := b64.StdEncoding.DecodeString(inputEncodedURL)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	// Validate input URL
    99  	inputURL, err := xnet.ParseHTTPURL(string(inputURLDecoded))
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	// Ensure incoming url points to MinIO Server
   104  	minIOHost := getMinIOEndpoint()
   105  	if inputURL.Host != minIOHost {
   106  		return nil, ErrForbidden
   107  	}
   108  	return swag.String(string(inputURLDecoded)), nil
   109  }
   110  
   111  func url2BucketAndObject(u *url.URL) (bucketName, objectName string) {
   112  	tokens := splitStr(u.Path, "/", 3)
   113  	return tokens[1], tokens[2]
   114  }
   115  
   116  // splitStr splits a string into n parts, empty strings are added
   117  // if we are not able to reach n elements
   118  func splitStr(path, sep string, n int) []string {
   119  	splits := strings.SplitN(path, sep, n)
   120  	// Add empty strings if we found elements less than nr
   121  	for i := n - len(splits); i > 0; i-- {
   122  		splits = append(splits, "")
   123  	}
   124  	return splits
   125  }