diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx index 5982f760f..5a876ad89 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx @@ -88,6 +88,9 @@ const styles = (theme: Theme) => theaderSearchLabel: { color: theme.palette.grey["400"], }, + addBucket: { + marginRight: 8, + }, theaderSearch: { borderColor: theme.palette.grey["200"], "& .MuiInputBase-input": { @@ -102,9 +105,6 @@ const styles = (theme: Theme) => }, }, }, - addBucket: { - marginRight: 8, - }, actionHeaderItems: { "@media (min-width: 320px)": { marginTop: 8, diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/CreateFolderModal.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/CreateFolderModal.tsx index 985970320..26f4a7158 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/CreateFolderModal.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/CreateFolderModal.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper"; import { Button, Grid } from "@material-ui/core"; import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; @@ -54,24 +54,51 @@ const CreateFolderModal = ({ classes, }: ICreateFolder) => { const [pathUrl, setPathUrl] = useState(""); + const [nameInputError, setNameInputError] = useState(""); + const [isFormValid, setIsFormValid] = useState(false); - const currentPath = `${bucketName}/${folderName}`; + const currentPath = `${bucketName}/${atob(folderName)}`; const resetForm = () => { setPathUrl(""); }; const createProcess = () => { - const newPath = `/buckets/${bucketName}/browse/${ - folderName !== "" ? `${folderName}/` : "" - }${pathUrl}`; - + let folderPath = ""; + if (folderName !== "") { + const decodedFolderName = atob(folderName); + folderPath = decodedFolderName.endsWith("/") + ? decodedFolderName + : `${decodedFolderName}/`; + } + const newPath = `/buckets/${bucketName}/browse/${btoa( + `${folderPath}${pathUrl}` + )}/`; history.push(newPath); - setFileModeEnabled(false); onClose(); }; + const validPathURL = useCallback(() => { + const patternAgainst = /^[a-zA-Z0-9*'#-\[\]_/&.@\s()]+$/; // Only allow uppercase, numbers, dashes and underscores + if (patternAgainst.test(pathUrl)) { + setNameInputError(""); + return true; + } + setNameInputError( + "Please verify the folder path contains valid characters only (letters, numbers and some special characters)." + ); + return false; + }, [pathUrl]); + + useEffect(() => { + let valid = true; + if (pathUrl.trim().length === 0 || !validPathURL()) { + valid = false; + } + setIsFormValid(valid); + }, [pathUrl]); + return ( { setPathUrl(e.target.value); }} + required + error={nameInputError} /> @@ -106,7 +135,7 @@ const CreateFolderModal = ({ type="submit" variant="contained" color="primary" - disabled={pathUrl.trim() === ""} + disabled={!isFormValid} onClick={createProcess} > Go diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx index fab79459c..665144e53 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx @@ -325,12 +325,18 @@ const ListObjects = ({ if (rewindDate) { setLoadingRewind(true); const rewindParsed = rewindDate.toISOString(); - + let pathPrefix = ""; + if (internalPaths) { + const decodedPath = atob(internalPaths); + pathPrefix = decodedPath.endsWith("/") + ? decodedPath + : decodedPath + "/"; + } api .invoke( "GET", - `/api/v1/buckets/${bucketName}/rewind/${rewindParsed}?prefix=${ - internalPaths ? `${internalPaths}/` : "" + `/api/v1/buckets/${bucketName}/rewind/${rewindParsed}${ + pathPrefix ? `?prefix=${btoa(pathPrefix)}` : `` }` ) .then((res: RewindObjectList) => { @@ -364,17 +370,24 @@ const ListObjects = ({ useEffect(() => { if (loading) { - let extraPath = ""; + let pathPrefix = ""; if (internalPaths) { - extraPath = `?prefix=${internalPaths}/`; + const decodedPath = atob(internalPaths); + pathPrefix = decodedPath.endsWith("/") + ? decodedPath + : decodedPath + "/"; } - let currentTimestamp = Date.now() + 0; + let currentTimestamp = Date.now(); setLoadingStartTime(currentTimestamp); setLoadingMessage(defLoading); - api - .invoke("GET", `/api/v1/buckets/${bucketName}/objects${extraPath}`) + .invoke( + "GET", + `/api/v1/buckets/${bucketName}/objects${ + pathPrefix ? `?prefix=${btoa(pathPrefix)}` : `` + }` + ) .then((res: BucketObjectsList) => { const records: BucketObject[] = res.objects || []; const folders: BucketObject[] = []; @@ -389,19 +402,26 @@ const ListObjects = ({ files.push(record); } }); - const recordsInElement = [...folders, ...files]; - setRecords(recordsInElement); // In case no objects were retrieved, We check if item is a file - if (!res.objects && extraPath !== "") { + if (!res.objects && pathPrefix !== "") { if (rewindEnabled) { const rewindParsed = rewindDate.toISOString(); + + let pathPrefix = ""; + if (internalPaths) { + const decodedPath = atob(internalPaths); + pathPrefix = decodedPath.endsWith("/") + ? decodedPath + : decodedPath + "/"; + } + api .invoke( "GET", - `/api/v1/buckets/${bucketName}/rewind/${rewindParsed}?prefix=${ - internalPaths ? `${internalPaths}/` : "" + `/api/v1/buckets/${bucketName}/rewind/${rewindParsed}${ + pathPrefix ? `?prefix=${btoa(pathPrefix)}` : `` }` ) .then((res: RewindObjectList) => { @@ -426,7 +446,9 @@ const ListObjects = ({ api .invoke( "GET", - `/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}` + `/api/v1/buckets/${bucketName}/objects${ + internalPaths ? `?prefix=${internalPaths}` : `` + }` ) .then((res: BucketObjectsList) => { //It is a file since it has elements in the object, setting file flag and waiting for component mount @@ -497,7 +519,7 @@ const ListObjects = ({ setCreateFolderOpen(false); }; - const upload = (e: any, bucketName: string, path: string) => { + const upload = (e: any, bucketName: string, encodedPath: string) => { if ( e === null || e === undefined || @@ -509,12 +531,11 @@ const ListObjects = ({ e.preventDefault(); let files = e.target.files; let uploadUrl = `${baseUrl}/api/v1/buckets/${bucketName}/objects/upload`; - if (path !== "") { - const encodedPath = encodeURIComponent(path); + if (encodedPath !== "") { uploadUrl = `${uploadUrl}?prefix=${encodedPath}`; } let xhr = new XMLHttpRequest(); - const areMultipleFiles = files.length > 1 ? true : false; + const areMultipleFiles = files.length > 1; const errorMessage = `An error occurred while uploading the file${ areMultipleFiles ? "s" : "" }.`; @@ -602,30 +623,20 @@ const ListObjects = ({ }; const openPath = (idElement: string) => { - const currentPath = get(match, "url", `/buckets/${bucketName}`); - - // Element is a folder, we redirect to it - if (idElement.endsWith("/")) { - const idElementClean = idElement - .substr(0, idElement.length - 1) - .split("/"); - const lastIndex = idElementClean.length - 1; - const newPath = `${currentPath}/${idElementClean[lastIndex]}`; - - history.push(newPath); - return; - } - // Element is a file. we open details here - const pathInArray = idElement.split("/"); - const fileName = pathInArray[pathInArray.length - 1]; - const newPath = `${currentPath}/${fileName}`; - + const newPath = `/buckets/${bucketName}/browse${ + idElement ? `/${btoa(idElement)}` : `` + }`; history.push(newPath); return; }; const uploadObject = (e: any): void => { - upload(e, bucketName, `${internalPaths}/`); + let pathPrefix = ""; + if (internalPaths) { + const decodedPath = atob(internalPaths); + pathPrefix = decodedPath.endsWith("/") ? decodedPath : decodedPath + "/"; + } + upload(e, bucketName, btoa(pathPrefix)); }; const openPreview = (fileObject: BucketObject) => { @@ -884,7 +895,10 @@ const ListObjects = ({ const ccPath = internalPaths.split("/").pop(); - const pageTitle = ccPath !== "" ? ccPath : "/"; + const pageTitle = ccPath !== "" ? atob(ccPath) : "/"; + // console.log("pageTitle", pageTitle); + const currentPath = pageTitle.split("/").filter((i: string) => i !== ""); + // console.log("currentPath", currentPath); return ( @@ -896,7 +910,7 @@ const ListObjects = ({ dataObject={{ name: selectedPreview.name, last_modified: "", - version_id: selectedPreview.version_id, + version_id: selectedPreview.version_id || null, }} /> )} @@ -948,12 +962,14 @@ const ListObjects = ({ } - title={pageTitle} + title={ + currentPath.length > 0 ? currentPath[currentPath.length - 1] : "/" + } subTitle={ } diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/RewindEnable.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/RewindEnable.tsx index a24a830b0..988220c41 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/RewindEnable.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/RewindEnable.tsx @@ -107,7 +107,7 @@ const RewindEnable = ({ name="status" checked={rewindEnableButton} onChange={(e: React.ChangeEvent) => { - setRewindEnableButton(false); + setRewindEnableButton(e.target.checked); }} label={"Current Status"} indicatorLabels={["Enabled", "Disabled"]} diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx index 0236757d8..96ccd9e2b 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx @@ -247,6 +247,7 @@ const ObjectDetails = ({ const [selectedTag, setSelectedTag] = useState(["", ""]); const [legalholdOpen, setLegalholdOpen] = useState(false); const [actualInfo, setActualInfo] = useState(null); + const [objectToShare, setObjectToShare] = useState(null); const [versions, setVersions] = useState([]); const [filterVersion, setFilterVersion] = useState(""); const [deleteOpen, setDeleteOpen] = useState(false); @@ -255,17 +256,23 @@ const ObjectDetails = ({ const [selectedTab, setSelectedTab] = useState(0); const internalPaths = get(match.params, "subpaths", ""); + const internalPathsDecoded = atob(internalPaths) || ""; const bucketName = match.params["bucketName"]; - const allPathData = internalPaths.split("/"); - const currentItem = allPathData.pop(); + const allPathData = internalPathsDecoded.split("/"); + const currentItem = allPathData.pop() || ""; + + // calculate object name to display + let objectNameArray: string[] = []; + if (actualInfo) { + objectNameArray = actualInfo.name.split("/"); + } useEffect(() => { if (loadObjectData) { - const encodedPath = encodeURIComponent(internalPaths); api .invoke( "GET", - `/api/v1/buckets/${bucketName}/objects?prefix=${encodedPath}${ + `/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}${ distributedSetup ? "&with_versions=true" : "" }` ) @@ -299,11 +306,10 @@ const ObjectDetails = ({ useEffect(() => { if (metadataLoad) { - const encodedPath = encodeURIComponent(internalPaths); api .invoke( "GET", - `/api/v1/buckets/${bucketName}/objects?prefix=${encodedPath}&with_metadata=true` + `/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}&with_metadata=true` ) .then((res: FileInfoResponse) => { const fileData = res.objects[0]; @@ -340,6 +346,7 @@ const ObjectDetails = ({ }; const closeShareModal = () => { + setObjectToShare(null); setShareFileModalOpen(false); }; @@ -367,8 +374,11 @@ const ObjectDetails = ({ const tableActions: ItemActions[] = [ { type: "share", - onClick: shareObject, - sendOnlyId: true, + onClick: (item: any) => { + setObjectToShare(item); + shareObject(); + }, + sendOnlyId: false, disableButtonFunction: (item: string) => { const element = versions.find((elm) => elm.version_id === item); if (element && element.is_delete_marker) { @@ -445,7 +455,7 @@ const ObjectDetails = ({ open={shareFileModalOpen} closeModalAndRefresh={closeShareModal} bucketName={bucketName} - dataObject={actualInfo} + dataObject={objectToShare || actualInfo} /> )} {retentionModalOpen && actualInfo && ( @@ -479,7 +489,7 @@ const ObjectDetails = ({ @@ -512,12 +522,16 @@ const ObjectDetails = ({ } - title={currentItem} + title={ + objectNameArray.length > 0 + ? objectNameArray[objectNameArray.length - 1] + : actualInfo.name + } subTitle={ } @@ -655,7 +669,7 @@ const ObjectDetails = ({ {actualInfo.retention_mode ? actualInfo.retention_mode.toLowerCase() - : "Undefined"} + : "None"} { diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/SetRetention.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/SetRetention.tsx index dc552524c..f38db842a 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/SetRetention.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/SetRetention.tsx @@ -119,7 +119,9 @@ const SetRetention = ({ api .invoke( "PUT", - `/api/v1/buckets/${bucketName}/objects/retention?prefix=${selectedObject}&version_id=${versionId}`, + `/api/v1/buckets/${bucketName}/objects/retention?prefix=${btoa( + selectedObject + )}&version_id=${versionId}`, { expires: expireDate, mode: type, @@ -142,7 +144,9 @@ const SetRetention = ({ api .invoke( "DELETE", - `/api/v1/buckets/${bucketName}/objects/retention?prefix=${selectedObject}&version_id=${versionId}` + `/api/v1/buckets/${bucketName}/objects/retention?prefix=${btoa( + selectedObject + )}&version_id=${versionId}` ) .then(() => { setIsSaving(false); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx index ad1c85c05..dabd3f25a 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx @@ -100,9 +100,9 @@ const ShareFile = ({ api .invoke( "GET", - `/api/v1/buckets/${bucketName}/objects/share?prefix=${ + `/api/v1/buckets/${bucketName}/objects/share?prefix=${btoa( dataObject.name - }&version_id=${versID || "null"}${ + )}&version_id=${versID || "null"}${ selectedDate !== "" ? `&expires=${diffDate}ms` : "" }` ) diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/Preview/PreviewFileContent.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/Preview/PreviewFileContent.tsx index fa167f3bb..e39463b03 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/Preview/PreviewFileContent.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/Preview/PreviewFileContent.tsx @@ -72,7 +72,7 @@ const PreviewFile = ({ let path = ""; if (object) { - const encodedPath = encodeURIComponent(object.name); + const encodedPath = btoa(object.name); path = `${window.location.origin}/api/v1/buckets/${bucketName}/objects/download?preview=true&prefix=${encodedPath}`; if (object.version_id) { path = path.concat(`&version_id=${object.version_id}`); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts index 9ddd48291..1797700a5 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts @@ -14,8 +14,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { isNullOrUndefined } from "util"; - export const download = ( bucketName: string, objectPath: string, @@ -25,9 +23,9 @@ export const download = ( ) => { const anchor = document.createElement("a"); document.body.appendChild(anchor); - const encodedPath = encodeURIComponent(objectPath); + const encodedPath = btoa(objectPath); let path = `/api/v1/buckets/${bucketName}/objects/download?prefix=${encodedPath}`; - if (!isNullOrUndefined(versionID) && versionID !== "null") { + if (versionID) { path = path.concat(`&version_id=${versionID}`); } window.location.href = path; diff --git a/portal-ui/src/screens/Console/ObjectBrowser/BrowserBreadcrumbs.tsx b/portal-ui/src/screens/Console/ObjectBrowser/BrowserBreadcrumbs.tsx index 1246ceda0..4f1be03b3 100644 --- a/portal-ui/src/screens/Console/ObjectBrowser/BrowserBreadcrumbs.tsx +++ b/portal-ui/src/screens/Console/ObjectBrowser/BrowserBreadcrumbs.tsx @@ -55,20 +55,16 @@ const BrowserBreadcrumbs = ({ paths = `/${internalPaths}`; } - const splitPaths = paths.split("/"); - + const splitPaths = paths.split("/").filter((path) => path !== ""); const listBreadcrumbs = splitPaths.map( (objectItem: string, index: number) => { - const subSplit = splitPaths.slice(1, index + 1).join("/"); - - const route = `/buckets/${bucketName}/browse${ - objectItem !== "" ? `/${subSplit}` : "" + const subSplit = splitPaths.slice(0, index + 1).join("/"); + const route = `/buckets/${bucketName}/browse/${ + subSplit ? `${btoa(subSplit)}` : `` }`; - const label = objectItem === "" ? bucketName : objectItem; - return ( - {label} + {objectItem} {index < splitPaths.length - 1 && / } ); @@ -95,6 +91,10 @@ const BrowserBreadcrumbs = ({ )} + + {bucketName} + {listBreadcrumbs.length > 0 && / } + {listBreadcrumbs} diff --git a/restapi/user_buckets.go b/restapi/user_buckets.go index 76cdd0b28..e1861d956 100644 --- a/restapi/user_buckets.go +++ b/restapi/user_buckets.go @@ -18,6 +18,7 @@ package restapi import ( "context" + "encoding/base64" "encoding/json" "fmt" "strings" @@ -835,13 +836,15 @@ func getBucketObjectLockingResponse(session *models.Principal, bucketName string func getBucketRewindResponse(session *models.Principal, params user_api.GetBucketRewindParams) (*models.RewindResponse, *models.Error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) defer cancel() - var prefix = "" - if params.Prefix != nil { - prefix = *params.Prefix + encodedPrefix := *params.Prefix + decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) + if err != nil { + return nil, prepareError(err) + } + prefix = string(decodedPrefix) } - s3Client, err := newS3BucketClient(session, params.BucketName, prefix) if err != nil { LogError("error creating S3Client: %v", err) diff --git a/restapi/user_objects.go b/restapi/user_objects.go index e6f292886..f4f734df6 100644 --- a/restapi/user_objects.go +++ b/restapi/user_objects.go @@ -18,6 +18,7 @@ package restapi import ( "context" + "encoding/base64" "fmt" "io" "log" @@ -83,8 +84,21 @@ func registerObjectsHandlers(api *operations.ConsoleAPI) { defer resp.Close() // indicate it's a download / inline content to the browser, and the size of the object - filename := params.Prefix + var prefixPath string + var filename string + if params.Prefix != "" { + encodedPrefix := params.Prefix + decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) + if err != nil { + log.Println(err) + } + prefixPath = string(decodedPrefix) + } + prefixElements := strings.Split(prefixPath, "/") + if len(prefixElements) > 0 { + filename = prefixElements[len(prefixElements)-1] + } if isPreview { rw.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", filename)) rw.Header().Set("X-Frame-Options", "SAMEORIGIN") @@ -172,9 +186,13 @@ func getListObjectsResponse(session *models.Principal, params user_api.ListObjec var recursive bool var withVersions bool var withMetadata bool - if params.Prefix != nil { - prefix = *params.Prefix + encodedPrefix := *params.Prefix + decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) + if err != nil { + return nil, prepareError(err) + } + prefix = string(decodedPrefix) } if params.Recursive != nil { recursive = *params.Recursive @@ -270,7 +288,16 @@ func listBucketObjects(ctx context.Context, client MinioClient, bucketName strin func getDownloadObjectResponse(session *models.Principal, params user_api.DownloadObjectParams) (io.ReadCloser, *models.Error) { ctx := context.Background() - s3Client, err := newS3BucketClient(session, params.BucketName, params.Prefix) + var prefix string + if params.Prefix != "" { + encodedPrefix := params.Prefix + decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) + if err != nil { + return nil, prepareError(err) + } + prefix = string(decodedPrefix) + } + s3Client, err := newS3BucketClient(session, params.BucketName, prefix) if err != nil { return nil, prepareError(err) } @@ -465,7 +492,12 @@ func getUploadObjectResponse(session *models.Principal, params user_api.PostBuck func uploadFiles(ctx context.Context, client MinioClient, params user_api.PostBucketsBucketNameObjectsUploadParams) error { var prefix string if params.Prefix != nil { - prefix = *params.Prefix + encodedPrefix := *params.Prefix + decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) + if err != nil { + return err + } + prefix = string(decodedPrefix) } // parse a request body as multipart/form-data. @@ -507,7 +539,16 @@ func uploadFiles(ctx context.Context, client MinioClient, params user_api.PostBu // getShareObjectResponse returns a share object url func getShareObjectResponse(session *models.Principal, params user_api.ShareObjectParams) (*string, *models.Error) { ctx := context.Background() - s3Client, err := newS3BucketClient(session, params.BucketName, params.Prefix) + var prefix string + if params.Prefix != "" { + encodedPrefix := params.Prefix + decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) + if err != nil { + return nil, prepareError(err) + } + prefix = string(decodedPrefix) + } + s3Client, err := newS3BucketClient(session, params.BucketName, prefix) if err != nil { return nil, prepareError(err) } @@ -552,7 +593,16 @@ func getSetObjectLegalHoldResponse(session *models.Principal, params user_api.Pu // create a minioClient interface implementation // defining the client to be used minioClient := minioClient{client: mClient} - err = setObjectLegalHold(ctx, minioClient, params.BucketName, params.Prefix, params.VersionID, *params.Body.Status) + var prefix string + if params.Prefix != "" { + encodedPrefix := params.Prefix + decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) + if err != nil { + return prepareError(err) + } + prefix = string(decodedPrefix) + } + err = setObjectLegalHold(ctx, minioClient, params.BucketName, prefix, params.VersionID, *params.Body.Status) if err != nil { return prepareError(err) } @@ -579,7 +629,16 @@ func getSetObjectRetentionResponse(session *models.Principal, params user_api.Pu // create a minioClient interface implementation // defining the client to be used minioClient := minioClient{client: mClient} - err = setObjectRetention(ctx, minioClient, params.BucketName, params.VersionID, params.Prefix, params.Body) + var prefix string + if params.Prefix != "" { + encodedPrefix := params.Prefix + decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) + if err != nil { + return prepareError(err) + } + prefix = string(decodedPrefix) + } + err = setObjectRetention(ctx, minioClient, params.BucketName, params.VersionID, prefix, params.Body) if err != nil { return prepareError(err) } @@ -623,7 +682,16 @@ func deleteObjectRetentionResponse(session *models.Principal, params user_api.De // create a minioClient interface implementation // defining the client to be used minioClient := minioClient{client: mClient} - err = deleteObjectRetention(ctx, minioClient, params.BucketName, params.Prefix, params.VersionID) + var prefix string + if params.Prefix != "" { + encodedPrefix := params.Prefix + decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) + if err != nil { + return prepareError(err) + } + prefix = string(decodedPrefix) + } + err = deleteObjectRetention(ctx, minioClient, params.BucketName, prefix, params.VersionID) if err != nil { return prepareError(err) } @@ -649,7 +717,16 @@ func getPutObjectTagsResponse(session *models.Principal, params user_api.PutObje // create a minioClient interface implementation // defining the client to be used minioClient := minioClient{client: mClient} - err = putObjectTags(ctx, minioClient, params.BucketName, params.Prefix, params.VersionID, params.Body.Tags) + var prefix string + if params.Prefix != "" { + encodedPrefix := params.Prefix + decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix) + if err != nil { + return prepareError(err) + } + prefix = string(decodedPrefix) + } + err = putObjectTags(ctx, minioClient, params.BucketName, prefix, params.VersionID, params.Body.Tags) if err != nil { return prepareError(err) }