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:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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"]}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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` : ""
|
||||
}`
|
||||
)
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user