diff --git a/Teknik/Areas/Upload/Controllers/UploadController.cs b/Teknik/Areas/Upload/Controllers/UploadController.cs index ba0462e..78f87d4 100644 --- a/Teknik/Areas/Upload/Controllers/UploadController.cs +++ b/Teknik/Areas/Upload/Controllers/UploadController.cs @@ -142,6 +142,7 @@ namespace Teknik.Areas.Upload.Controllers string iv = string.Empty; string contentType = string.Empty; long contentLength = 0; + bool userUploaded = false; DateTime dateUploaded = new DateTime(); using (TeknikEntities db = new TeknikEntities()) @@ -160,6 +161,7 @@ namespace Teknik.Areas.Upload.Controllers contentType = uploads.ContentType; contentLength = uploads.ContentLength; dateUploaded = uploads.DateUploaded; + userUploaded = uploads.User != null; } else { @@ -176,6 +178,19 @@ namespace Teknik.Areas.Upload.Controllers model.ContentType = contentType; model.ContentLength = contentLength; model.IV = iv; + model.Decrypt = true; + + return View(model); + } + else if (!userUploaded && Config.UploadConfig.MaxDownloadSize < contentLength) + { + // We want to force them to the dl page due to them being over the max download size for embedded content + DownloadViewModel model = new DownloadViewModel(); + model.CurrentSub = Subdomain; + model.FileName = file; + model.ContentType = contentType; + model.ContentLength = contentLength; + model.Decrypt = false; return View(model); } @@ -296,9 +311,6 @@ namespace Teknik.Areas.Upload.Controllers Response.AddHeader("Content-Disposition", cd.ToString()); - // Apply content security policy for downloads - //Response.AddHeader("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; font-src *; connect-src 'self'; media-src 'self'; child-src 'self'; form-action 'none';"); - // Read in the file FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); @@ -341,7 +353,7 @@ namespace Teknik.Areas.Upload.Controllers [HttpPost] [AllowAnonymous] - public FileResult DownloadData(string file) + public ActionResult DownloadData(string file, bool decrypt) { if (Config.UploadConfig.DownloadEnabled) { @@ -354,16 +366,46 @@ namespace Teknik.Areas.Upload.Controllers string filePath = Path.Combine(Config.UploadConfig.UploadDirectory, subDir, upload.FileName); if (System.IO.File.Exists(filePath)) { - FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); - return File(fileStream, System.Net.Mime.MediaTypeNames.Application.Octet, file); + // Notify the client the content length we'll be outputting + Response.AddHeader("Content-Length", upload.ContentLength.ToString()); + + // Create content disposition + var cd = new System.Net.Mime.ContentDisposition + { + FileName = upload.Url, + Inline = true + }; + + Response.AddHeader("Content-Disposition", cd.ToString()); + + // Read in the file + FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + + // If the IV is set, and Key is set, then decrypt it while sending + if (decrypt && !string.IsNullOrEmpty(upload.Key) && !string.IsNullOrEmpty(upload.IV)) + { + byte[] keyBytes = Encoding.UTF8.GetBytes(upload.Key); + byte[] ivBytes = Encoding.UTF8.GetBytes(upload.IV); + + return new FileGenerateResult(upload.Url, + upload.ContentType, + (response) => ResponseHelper.StreamToOutput(response, true, new AesCounterStream(fs, false, keyBytes, ivBytes), (int)upload.ContentLength, Config.UploadConfig.ChunkSize), + false); + } + else // Otherwise just send it + { + // Send the file + return new FileGenerateResult(upload.Url, + upload.ContentType, + (response) => ResponseHelper.StreamToOutput(response, true, fs, (int)upload.ContentLength, Config.UploadConfig.ChunkSize), + false); + } } } - Redirect(Url.SubRouteUrl("error", "Error.Http404")); - return null; + return Json(new { error = new { message = "File Does Not Exist" } }); } } - Redirect(Url.SubRouteUrl("error", "Error.Http403")); - return null; + return Json(new { error = new { message = "Downloads are disabled" } }); } [HttpGet] diff --git a/Teknik/Areas/Upload/Scripts/Download.js b/Teknik/Areas/Upload/Scripts/Download.js index e38efbc..fda15ac 100644 --- a/Teknik/Areas/Upload/Scripts/Download.js +++ b/Teknik/Areas/Upload/Scripts/Download.js @@ -1,42 +1,81 @@ -$(document).ready(downloadFile); +$(document).ready(downloadFile); function downloadFile() { var key = window.location.hash.substring(1); - if (key == null || key == '') { + if (decrypt && (key == null || key == '')) { bootbox.prompt("Enter the file's private key", function (result) { if (result) { key = result; } - processDownload(key); + processDownload(key, iv, decrypt); }); } else { - processDownload(key); + processDownload(key, iv, decrypt); } } -function processDownload(key) { - if (key !== null && key !== '' && iv !== null && iv !== '') { - // speed info - var lastTime = (new Date()).getTime(); - var lastData = 0; +function processDownload(key, iv, decrypt) { + // speed info + var startTime = (new Date()).getTime(); - var fd = new FormData(); - fd.append('file', fileName); - fd.append('__RequestVerificationToken', $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val()); + var fd = new FormData(); + fd.append('file', fileName); + fd.append('decrypt', !decrypt); + fd.append('__RequestVerificationToken', $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val()); - var xhr = new XMLHttpRequest(); - xhr.open('POST', downloadDataUrl, true); - xhr.responseType = 'arraybuffer'; + var xhr = new XMLHttpRequest(); + xhr.open('POST', downloadDataUrl, true); + xhr.responseType = 'arraybuffer'; - xhr.onload = function (e) { - if (this.status == 200) { - lastTime = (new Date()).getTime(); - lastData = 0; + xhr.onload = function(e) { + if (this.status === 200) { + decryptDownload(this.response, key, iv, decrypt); + } + }; - var worker = new Worker(GenerateBlobURL(encScriptSrc)); + xhr.onprogress = function (e) { + if (e.lengthComputable) { + var curTime = (new Date()).getTime(); + var elapsedTime = (curTime - startTime) / 1000; + var speed = (e.loaded / elapsedTime); + var percentComplete = Math.round(e.loaded * 100 / e.total); + setProgress(percentComplete, + 'progress-bar-success progress-bar-striped active', + percentComplete + '%', + 'Downloading File [' + + getReadableFileSizeString(e.loaded) + + ' / ' + + getReadableFileSizeString(e.total) + + ' @ ' + + getReadableBandwidthString(speed * 8) + + ']'); + } + } - worker.addEventListener('message', function (e) { + xhr.onerror = function(e) { + setProgress(100, 'progress-bar-danger', '', 'Download Failed'); + }; + + xhr.onabort = function(e) { + setProgress(100, 'progress-bar-warning', '', 'Download Aborted'); + }; + + xhr.send(fd); +} + +function decryptDownload(fileData, key, iv, decrypt) { + // speed info + var lastTime = (new Date()).getTime(); + var lastData = 0; + + // Do we need to decrypt the download? + if (decrypt) { + if (key !== null && key !== '' && iv !== null && iv !== '') { + var worker = new Worker(GenerateBlobURL(encScriptSrc)); + + worker.addEventListener('message', + function (e) { switch (e.data.cmd) { case 'progress': var curTime = (new Date()).getTime(); @@ -46,11 +85,20 @@ function processDownload(key) { lastTime = curTime; lastData = e.data.processed; var percentComplete = Math.round(e.data.processed * 100 / e.data.total); - setProgress(percentComplete, 'progress-bar-success progress-bar-striped active', percentComplete + '%', 'Decrypting [' + getReadableFileSizeString(e.data.processed) + ' / ' + getReadableFileSizeString(e.data.total) + ' @ ' + getReadableBandwidthString(speed * 8) + ']'); + setProgress(percentComplete, + 'progress-bar-success progress-bar-striped active', + percentComplete + '%', + 'Decrypting [' + + getReadableFileSizeString(e.data.processed) + + ' / ' + + getReadableFileSizeString(e.data.total) + + ' @ ' + + getReadableBandwidthString(speed * 8) + + ']'); } break; case 'finish': - setProgress(100, 'progress-bar-success', '', 'Complete'); + setProgress(100, 'progress-bar-success', 'Complete', ''); if (fileType == null || fileType == '') { fileType = "application/octet-stream"; } @@ -62,55 +110,54 @@ function processDownload(key) { } }); - worker.onerror = function (err) { - // An error occured - setProgress(100, 'progress-bar-danger', '', 'Error Occured'); - } - - // Create a blob for the aes script - var scriptBlob = GenerateBlobURL(aesScriptSrc); - - // Execute worker with data - var objData = - { - cmd: 'decrypt', - script: scriptBlob, - key: key, - iv: iv, - chunkSize: chunkSize, - file: this.response - }; - worker.postMessage(objData, [objData.file]); + worker.onerror = function (err) { + // An error occured + setProgress(100, 'progress-bar-danger', '', 'Error Occured'); } - }; - xhr.onprogress = function (e) { - if (e.lengthComputable) { - var curTime = (new Date()).getTime(); - var elapsedTime = (curTime - lastTime) / 1000; - var speed = ((e.loaded - lastData) / elapsedTime); - lastTime = curTime; - lastData = e.loaded; - var percentComplete = Math.round(e.loaded * 100 / e.total); - setProgress(percentComplete, 'progress-bar-success progress-bar-striped active', percentComplete + '%', 'Downloading File [' + getReadableFileSizeString(e.loaded) + ' / ' + getReadableFileSizeString(e.total) + ' @ ' + getReadableBandwidthString(speed * 8) + ']'); - } - }; + // Create a blob for the aes script + var scriptBlob = GenerateBlobURL(aesScriptSrc); - xhr.onerror = function (e) { - setProgress(100, 'progress-bar-danger', '', 'Download Failed'); - }; + // Execute worker with data + var objData = + { + cmd: 'decrypt', + script: scriptBlob, + key: key, + iv: iv, + chunkSize: chunkSize, + file: fileData + }; + worker.postMessage(objData, [objData.file]); + } else { + setProgress(100, 'progress-bar-danger', '', 'Private Key Needed'); + } + } else { + // We want to just prompt the file for DL + setProgress(100, 'progress-bar-success', 'Complete', ''); - xhr.onabort = function (e) { - setProgress(100, 'progress-bar-warning', '', 'Download Aborted'); - }; + // Convert file to blob + if (fileType == null || fileType == '') { + fileType = "application/octet-stream"; + } + var blob = new Blob([fileData], { type: fileType }); - xhr.send(fd); - } - else { - setProgress(100, 'progress-bar-danger', '', 'Private Key Needed'); + // Add the file download link + addDownloadLink(fileData, key, iv, decrypt); + + saveAs(blob, fileName); } } +function addDownloadLink(fileData, key, iv, decrypt) { + + var newItem = $(''); + newItem.click(function() { + decryptDownload(fileData, key, iv, decrypt); + }); + $('#progress-panel').find('#progress-info').append(newItem); +} + function setProgress(percentage, classes, barMessage, title) { var progress = $('#progress-panel'); if (progress !== null) { @@ -121,4 +168,4 @@ function setProgress(percentage, classes, barMessage, title) { progress.find('#progress-bar').html(barMessage); progress.find('#progress-info').html(title); } -} \ No newline at end of file +} diff --git a/Teknik/Areas/Upload/Scripts/Upload.js b/Teknik/Areas/Upload/Scripts/Upload.js index 536c57d..d0e1d61 100644 --- a/Teknik/Areas/Upload/Scripts/Upload.js +++ b/Teknik/Areas/Upload/Scripts/Upload.js @@ -306,8 +306,7 @@ function encryptFile(blob, fileName, contentType, ID, callback) { function uploadFile(data, key, iv, filetype, fileExt, fileID, encrypt) { // Set variables for tracking - var lastTime = (new Date()).getTime(); - var lastData = 0; + var startTime = (new Date()).getTime(); var blob = new Blob([data]); // Now we need to upload the file @@ -323,7 +322,7 @@ function uploadFile(data, key, iv, filetype, fileExt, fileID, encrypt) fd.append('__RequestVerificationToken', $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val()); var xhr = new XMLHttpRequest(); - xhr.upload.addEventListener("progress", uploadProgress.bind(null, fileID, lastTime, lastData), false); + xhr.upload.addEventListener("progress", uploadProgress.bind(null, fileID, startTime), false); xhr.addEventListener("load", uploadComplete.bind(null, fileID, key, encrypt), false); xhr.addEventListener("error", uploadFailed.bind(null, fileID), false); xhr.addEventListener("abort", uploadCanceled.bind(null, fileID), false); @@ -333,13 +332,11 @@ function uploadFile(data, key, iv, filetype, fileExt, fileID, encrypt) -function uploadProgress(fileID, lastTime, lastData, evt) { +function uploadProgress(fileID, startTime, evt) { if (evt.lengthComputable) { var curTime = (new Date()).getTime(); - var elapsedTime = (curTime - lastTime) / 1000; - var speed = ((evt.loaded - lastData) / elapsedTime); - lastTime = curTime; - lastData = evt.loaded; + var elapsedTime = (curTime - startTime) / 1000; + var speed = (evt.loaded / elapsedTime); var percentComplete = Math.round(evt.loaded * 100 / evt.total); if (percentComplete == 100) { setProgress(fileID, 100, 'progress-bar-success progress-bar-striped active', '', 'Processing Upload'); diff --git a/Teknik/Areas/Upload/ViewModels/DownloadViewModel.cs b/Teknik/Areas/Upload/ViewModels/DownloadViewModel.cs index 15320d1..657b0d4 100644 --- a/Teknik/Areas/Upload/ViewModels/DownloadViewModel.cs +++ b/Teknik/Areas/Upload/ViewModels/DownloadViewModel.cs @@ -13,7 +13,6 @@ namespace Teknik.Areas.Upload.ViewModels public string ContentType { get; set; } public long ContentLength { get; set; } public string IV { get; set; } - public int keySize { get; set; } - public int blockSize { get; set; } + public bool Decrypt { get; set; } } } diff --git a/Teknik/Areas/Upload/Views/Upload/Download.cshtml b/Teknik/Areas/Upload/Views/Upload/Download.cshtml index 138cf1d..42836c9 100644 --- a/Teknik/Areas/Upload/Views/Upload/Download.cshtml +++ b/Teknik/Areas/Upload/Views/Upload/Download.cshtml @@ -9,6 +9,7 @@ var fileName = '@Model.FileName'; var fileType = '@Model.ContentType'; var iv = '@Model.IV'; + var decrypt = @Model.Decrypt.ToString().ToLower(); var chunkSize = @(Model.Config.UploadConfig.ChunkSize); diff --git a/Utilities/Configuration/UploadConfig.cs b/Utilities/Configuration/UploadConfig.cs index 4032e0c..6c248eb 100644 --- a/Utilities/Configuration/UploadConfig.cs +++ b/Utilities/Configuration/UploadConfig.cs @@ -16,6 +16,8 @@ namespace Teknik.Configuration public long MaxUploadSizeBasic { get; set; } // Max Upload Size for premium users public long MaxUploadSizePremium { get; set; } + // Gets the maximum download size before they are forced to the download page + public long MaxDownloadSize { get; set; } // Location of the upload directory public string UploadDirectory { get; set; } // File Extension for saved files @@ -46,6 +48,7 @@ namespace Teknik.Configuration MaxUploadSize = 100000000; MaxUploadSizeBasic = 100000000; MaxUploadSizePremium = 100000000; + MaxDownloadSize = 100000000; UploadDirectory = Directory.GetCurrentDirectory(); FileExtension = "enc"; UrlLength = 5;