Multiple fixes for sub path and objects filename encoding (#1086)

- fix: objects with special characters (ie: /,&,%,*) won't open
- fix: create subdolders with special characters won't work, ie: /,&,%,*
- fix: view subfolders with special characters (ie: /,&,%,*) won't work
- refactor: browser breadcrumb
- fix: rewind enable/disable toggle button not working
- fix: undefined style for add bucket button in buckets page
- Added: validation for folder path naming
- refactor: encode prefix parameter using base64 to avoid url encode
  issues
- fix: share link for versioned object won't work because of wrong
  version_id

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
Lenin Alevski
2021-09-28 12:25:28 -07:00
committed by GitHub
parent 2269af829f
commit 3d27cd2bd3
13 changed files with 243 additions and 100 deletions

View File

@@ -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,

View File

@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
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<string>("");
const [isFormValid, setIsFormValid] = useState<boolean>(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 (
<React.Fragment>
<ModalWrapper
@@ -91,6 +118,8 @@ const CreateFolderModal = ({
onChange={(e) => {
setPathUrl(e.target.value);
}}
required
error={nameInputError}
/>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
@@ -106,7 +135,7 @@ const CreateFolderModal = ({
type="submit"
variant="contained"
color="primary"
disabled={pathUrl.trim() === ""}
disabled={!isFormValid}
onClick={createProcess}
>
Go

View File

@@ -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 (
<React.Fragment>
@@ -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 = ({
<FolderIcon width={40} />
</Fragment>
}
title={pageTitle}
title={
currentPath.length > 0 ? currentPath[currentPath.length - 1] : "/"
}
subTitle={
<Fragment>
<BrowserBreadcrumbs
bucketName={bucketName}
internalPaths={internalPaths}
internalPaths={pageTitle}
/>
</Fragment>
}

View File

@@ -107,7 +107,7 @@ const RewindEnable = ({
name="status"
checked={rewindEnableButton}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setRewindEnableButton(false);
setRewindEnableButton(e.target.checked);
}}
label={"Current Status"}
indicatorLabels={["Enabled", "Disabled"]}

View File

@@ -247,6 +247,7 @@ const ObjectDetails = ({
const [selectedTag, setSelectedTag] = useState<string[]>(["", ""]);
const [legalholdOpen, setLegalholdOpen] = useState<boolean>(false);
const [actualInfo, setActualInfo] = useState<IFileInfo | null>(null);
const [objectToShare, setObjectToShare] = useState<IFileInfo | null>(null);
const [versions, setVersions] = useState<IFileInfo[]>([]);
const [filterVersion, setFilterVersion] = useState<string>("");
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
@@ -255,17 +256,23 @@ const ObjectDetails = ({
const [selectedTab, setSelectedTab] = useState<number>(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 = ({
<DeleteTagModal
deleteOpen={deleteTagModalOpen}
currentTags={actualInfo.tags}
selectedObject={internalPaths}
selectedObject={actualInfo.name}
versionId={actualInfo.version_id}
bucketName={bucketName}
onCloseAndUpdate={closeDeleteTagModal}
@@ -490,7 +500,7 @@ const ObjectDetails = ({
<SetLegalHoldModal
open={legalholdOpen}
closeModalAndRefresh={closeLegalholdModal}
objectName={internalPaths}
objectName={actualInfo.name}
bucketName={bucketName}
actualInfo={actualInfo}
/>
@@ -512,12 +522,16 @@ const ObjectDetails = ({
<ObjectBrowserIcon width={40} />
</Fragment>
}
title={currentItem}
title={
objectNameArray.length > 0
? objectNameArray[objectNameArray.length - 1]
: actualInfo.name
}
subTitle={
<Fragment>
<BrowserBreadcrumbs
bucketName={bucketName}
internalPaths={internalPaths}
internalPaths={actualInfo.name}
/>
</Fragment>
}
@@ -655,7 +669,7 @@ const ObjectDetails = ({
<td className={classes.capitalizeFirst}>
{actualInfo.retention_mode
? actualInfo.retention_mode.toLowerCase()
: "Undefined"}
: "None"}
<IconButton
color="primary"
aria-label="retention"

View File

@@ -76,7 +76,9 @@ const SetLegalHoldModal = ({
api
.invoke(
"PUT",
`/api/v1/buckets/${bucketName}/objects/legalhold?prefix=${objectName}&version_id=${versionId}`,
`/api/v1/buckets/${bucketName}/objects/legalhold?prefix=${btoa(
objectName
)}&version_id=${versionId}`,
{ status: legalHoldEnabled ? "enabled" : "disabled" }
)
.then(() => {

View File

@@ -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);

View File

@@ -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` : ""
}`
)

View File

@@ -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}`);

View File

@@ -14,8 +14,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
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;

View File

@@ -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 (
<React.Fragment key={`breadcrumbs-${index.toString()}`}>
<Link to={route}>{label}</Link>
<Link to={route}>{objectItem}</Link>
{index < splitPaths.length - 1 && <span> / </span>}
</React.Fragment>
);
@@ -95,6 +91,10 @@ const BrowserBreadcrumbs = ({
)}
<Grid item xs={12} className={classes.breadcrumbs}>
<React.Fragment>
<Link to={`/buckets/${bucketName}/browse`}>{bucketName}</Link>
{listBreadcrumbs.length > 0 && <span> / </span>}
</React.Fragment>
{listBreadcrumbs}
</Grid>
</React.Fragment>

View File

@@ -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)

View File

@@ -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)
}