Merge pull request #5404 from sbwalker/dev
add new option to FileManager component to anonymize filenames during upload
This commit is contained in:
@ -157,6 +157,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool AnonymizeUploadFilenames { get; set; } = false; // optional - indicate if file names should be anonymized on upload - default false
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
||||||
|
|
||||||
@ -408,7 +411,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// upload files
|
// upload files
|
||||||
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, tokenSource.Token);
|
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, AnonymizeUploadFilenames, tokenSource.Token);
|
||||||
|
|
||||||
// reset progress indicators
|
// reset progress indicators
|
||||||
if (ShowProgress)
|
if (ShowProgress)
|
||||||
|
@ -224,17 +224,17 @@ namespace Oqtane.UI
|
|||||||
|
|
||||||
public Task UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt)
|
public Task UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt)
|
||||||
{
|
{
|
||||||
UploadFiles(posturl, folder, id, antiforgerytoken, jwt, 1);
|
UploadFiles(posturl, folder, id, antiforgerytoken, jwt, 1, false);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<bool> UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt, int chunksize, CancellationToken cancellationToken = default)
|
public ValueTask<bool> UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt, int chunksize, bool anonymizeuploadfilenames, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return _jsRuntime.InvokeAsync<bool>(
|
return _jsRuntime.InvokeAsync<bool>(
|
||||||
"Oqtane.Interop.uploadFiles", cancellationToken,
|
"Oqtane.Interop.uploadFiles", cancellationToken,
|
||||||
posturl, folder, id, antiforgerytoken, jwt, chunksize);
|
posturl, folder, id, antiforgerytoken, jwt, chunksize, anonymizeuploadfilenames);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -444,9 +444,14 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ensure filename is valid
|
// ensure filename is valid
|
||||||
if (!formfile.FileName.IsPathOrFileValid() || !HasValidFileExtension(formfile.FileName))
|
string fileName = formfile.FileName;
|
||||||
|
if (Path.GetExtension(fileName).Contains(':'))
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName);
|
fileName = fileName.Substring(0, fileName.LastIndexOf(':')); // remove invalid suffix from extension
|
||||||
|
}
|
||||||
|
if (!fileName.IsPathOrFileValid() || !HasValidFileExtension(fileName))
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload File Name Is Invalid Or Contains Invalid Extension {File}", fileName);
|
||||||
return StatusCode((int)HttpStatusCode.Forbidden);
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,8 +463,8 @@ namespace Oqtane.Controllers
|
|||||||
return StatusCode((int)HttpStatusCode.Forbidden);
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create file name using header values
|
// create file name using header part values
|
||||||
string fileName = formfile.FileName + ".part_" + partCount.ToString("000") + "_" + totalParts.ToString("000");
|
fileName += ".part_" + partCount.ToString("000") + "_" + totalParts.ToString("000");
|
||||||
string folderPath = "";
|
string folderPath = "";
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -532,13 +537,13 @@ namespace Oqtane.Controllers
|
|||||||
string parts = Path.GetExtension(filename)?.Replace(token, ""); // returns "001_999"
|
string parts = Path.GetExtension(filename)?.Replace(token, ""); // returns "001_999"
|
||||||
int totalparts = int.Parse(parts?.Substring(parts.IndexOf("_") + 1));
|
int totalparts = int.Parse(parts?.Substring(parts.IndexOf("_") + 1));
|
||||||
|
|
||||||
filename = Path.GetFileNameWithoutExtension(filename); // base filename
|
filename = Path.GetFileNameWithoutExtension(filename); // base filename including original file extension
|
||||||
string[] fileparts = Directory.GetFiles(folder, filename + token + "*"); // list of all file parts
|
string[] fileparts = Directory.GetFiles(folder, filename + token + "*"); // list of all file parts
|
||||||
|
|
||||||
// if all of the file parts exist (note that file parts can arrive out of order)
|
// if all of the file parts exist (note that file parts can arrive out of order)
|
||||||
if (fileparts.Length == totalparts && CanAccessFiles(fileparts))
|
if (fileparts.Length == totalparts && CanAccessFiles(fileparts))
|
||||||
{
|
{
|
||||||
// merge file parts into temp file (in case another user is trying to get the file)
|
// merge file parts into temp file (in case another user is trying to read the file)
|
||||||
bool success = true;
|
bool success = true;
|
||||||
using (var stream = new FileStream(Path.Combine(folder, filename + ".tmp"), FileMode.Create))
|
using (var stream = new FileStream(Path.Combine(folder, filename + ".tmp"), FileMode.Create))
|
||||||
{
|
{
|
||||||
@ -559,9 +564,7 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// clean up file parts
|
// clean up file parts
|
||||||
foreach (var file in Directory.GetFiles(folder, "*" + token + "*"))
|
foreach (var file in fileparts)
|
||||||
{
|
|
||||||
if (fileparts.Contains(file))
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -572,12 +575,11 @@ namespace Oqtane.Controllers
|
|||||||
// unable to delete part - ignore
|
// unable to delete part - ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// rename temp file
|
// rename temp file
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
// remove file if it already exists (as well as any thumbnails which may exist)
|
// remove existing file (as well as any thumbnails)
|
||||||
foreach (var file in Directory.GetFiles(folder, Path.GetFileNameWithoutExtension(filename) + ".*"))
|
foreach (var file in Directory.GetFiles(folder, Path.GetFileNameWithoutExtension(filename) + ".*"))
|
||||||
{
|
{
|
||||||
if (Path.GetExtension(file) != ".tmp")
|
if (Path.GetExtension(file) != ".tmp")
|
||||||
|
@ -311,7 +311,7 @@ Oqtane.Interop = {
|
|||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
},
|
},
|
||||||
uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize) {
|
uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize, anonymizeuploadfilenames) {
|
||||||
var success = true;
|
var success = true;
|
||||||
var fileinput = document.getElementById('FileInput_' + id);
|
var fileinput = document.getElementById('FileInput_' + id);
|
||||||
var progressinfo = document.getElementById('ProgressInfo_' + id);
|
var progressinfo = document.getElementById('ProgressInfo_' + id);
|
||||||
@ -344,16 +344,22 @@ Oqtane.Interop = {
|
|||||||
const totalParts = Math.ceil(file.size / chunkSize);
|
const totalParts = Math.ceil(file.size / chunkSize);
|
||||||
let partCount = 0;
|
let partCount = 0;
|
||||||
|
|
||||||
|
let filename = file.name;
|
||||||
|
if (anonymizeuploadfilenames) {
|
||||||
|
filename = crypto.randomUUID() + '.' + filename.split('.').pop();
|
||||||
|
}
|
||||||
|
|
||||||
const uploadPart = () => {
|
const uploadPart = () => {
|
||||||
const start = partCount * chunkSize;
|
const start = partCount * chunkSize;
|
||||||
const end = Math.min(start + chunkSize, file.size);
|
const end = Math.min(start + chunkSize, file.size);
|
||||||
const chunk = file.slice(start, end);
|
const chunk = file.slice(start, end);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
let formdata = new FormData();
|
let formdata = new FormData();
|
||||||
formdata.append('__RequestVerificationToken', antiforgerytoken);
|
formdata.append('__RequestVerificationToken', antiforgerytoken);
|
||||||
formdata.append('folder', folder);
|
formdata.append('folder', folder);
|
||||||
formdata.append('formfile', chunk, file.name);
|
formdata.append('formfile', chunk, filename);
|
||||||
|
|
||||||
var credentials = 'same-origin';
|
var credentials = 'same-origin';
|
||||||
var headers = new Headers();
|
var headers = new Headers();
|
||||||
|
Reference in New Issue
Block a user