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 }