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 }