Browse Source

Implemented file download and decryption. Still need to re-create file from ArrayBuffer.

tags/2.0.3
Teknikode 4 years ago
parent
commit
252d605ce0

+ 72
- 11
Teknik/Areas/Upload/Controllers/UploadController.cs View File

@@ -1,16 +1,20 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Teknik.Areas.Upload.Models;
using Teknik.Areas.Upload.ViewModels;
using Teknik.Controllers;
using Teknik.Models;

namespace Teknik.Areas.Upload.Controllers
{
public class UploadController : DefaultController
{
private TeknikEntities db = new TeknikEntities();

// GET: Upload/Upload
[HttpGet]
[AllowAnonymous]
@@ -19,33 +23,90 @@ namespace Teknik.Areas.Upload.Controllers
return View(new UploadViewModel());
}

// User did not supply key
[HttpGet]
[HttpPost]
[AllowAnonymous]
public ActionResult Download(string url)
[ValidateAntiForgeryToken]
public ActionResult Upload(string fileType, string iv, HttpPostedFileWrapper data)
{
return View(new UploadViewModel());
if (data.ContentLength <= Config.UploadConfig.MaxUploadSize)
{
Models.Upload upload = Uploader.SaveFile(data, fileType, iv);
if (upload != null)
{
return Json(new { result = new { name = upload.Url, url = Url.SubRouteUrl("upload", "Upload.Download", new { file = upload.Url }) } }, "text/plain");
}
return Json(new { error = "Unable to upload file" });
}
else
{
return Json(new { error = "File Too Large" });
}
}

// User supplied key
// User did not supply key
[HttpGet]
[AllowAnonymous]
public ActionResult Download(string url, string key)
public ActionResult Download(string file)
{
return View(new UploadViewModel());
Models.Upload upload = db.Uploads.Where(up => up.Url == file).FirstOrDefault();
if (upload != null)
{
// We don't have the key, so we need to decrypt it client side
if (upload.Key == null)
{
DownloadViewModel model = new DownloadViewModel();
model.FileName = file;
model.ContentType = upload.ContentType;
model.Key = upload.Key;
model.IV = upload.IV;

return View(model);
}
else
{
// decrypt it server side! Weee
return View();
}
}
else
{
return RedirectToRoute("Error.Http404");
}
}

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Upload(string fileType, string iv, HttpPostedFileWrapper data)
public FileResult DownloadData(string file)
{
Models.Upload upload = Uploader.SaveFile(data, fileType, iv);
Models.Upload upload = db.Uploads.Where(up => up.Url == file).FirstOrDefault();
if (upload != null)
{
return Json(new { result = new { name = upload.Url, url = Url.SubRouteUrl("upload", "Upload.Download.Key", new { file = upload.Url, key = "{key}" }), keyVar = "{key}" } }, "text/plain");
string filePath = Path.Combine(Config.UploadConfig.UploadDirectory, upload.FileName);
if (System.IO.File.Exists(filePath))
{
byte[] buffer;
FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
try
{
int length = (int)fileStream.Length; // get file length
buffer = new byte[length]; // create buffer
int count; // actual number of bytes read
int sum = 0; // total number of bytes read

// read until Read method returns 0 (end of the stream has been reached)
while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
sum += count; // sum is a buffer offset for next reading
}
finally
{
fileStream.Close();
}
return File(buffer, System.Net.Mime.MediaTypeNames.Application.Octet, file);
}
}
return Json(new { error = "Unable to upload file" });
RedirectToAction("Http404", "Error", new { area = "Errors", exception = new Exception("File Not Found") });
return null;
}

[HttpPost]

+ 55
- 0
Teknik/Areas/Upload/Scripts/Download.js View File

@@ -0,0 +1,55 @@
$(document).ready(downloadFile);

function downloadFile() {
var fd = new FormData();
fd.append('file', fileName);
fd.append('__RequestVerificationToken', $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val());

var xhr = new XMLHttpRequest();
xhr.open('POST', downloadDataUrl, true);
xhr.responseType = 'arraybuffer';

xhr.onload = function (e) {
if (this.status == 200) {
var worker = new Worker(encScriptSrc);

worker.addEventListener('message', function (e) {
switch (e.data.cmd) {
case 'progress':
var percentComplete = Math.round(e.data.processed * 100 / e.data.total);
$("#progress").children('.progress-bar').css('width', (percentComplete * (2 / 5)) + 20 + '%');
$("#progress").children('.progress-bar').html(percentComplete + '% Decrypted');
break;
case 'finish':
var blob = new Blob([e.data.buffer], { type: fileType });
var url = (window.webkitURL || window.URL).createObjectURL(blob);
location.href = url; // <-- Download!
// DO SOMETHING
break;
}
});

worker.onerror = function (err) {
// An error occured
$("#progress").children('.progress-bar').css('width', '100%');
$("#progress").children('.progress-bar').removeClass('progress-bar-success');
$("#progress").children('.progress-bar').addClass('progress-bar-danger');
$("#progress").children('.progress-bar').html('Error Occured');
}

// Execute worker with data
var objData =
{
cmd: 'decrypt',
script: aesScriptSrc,
key: key,
iv: iv,
chunkSize: chunkSize,
file: this.response
};
worker.postMessage(objData, [objData.file]);
}
};

xhr.send(fd);
}

+ 73
- 76
Teknik/Areas/Upload/Scripts/EncryptionWorker.js View File

@@ -1,88 +1,85 @@
self.addEventListener('message', function (e) {
importScripts(e.data.script);
var bytes = new Uint8Array(e.data.file);

var startByte = 0;
var endByte = 0;
var prog = [];

var key = CryptoJS.enc.Utf8.parse(e.data.key);
var iv = CryptoJS.enc.Utf8.parse(e.data.iv);

// Create aes encryptor object
var aesCrypto;
switch (e.data.cmd) {
case 'encrypt':
var bytes = new Uint8Array(e.data.file);

var startByte = 0;
var endByte = 0;
var prog = [];

var key = CryptoJS.enc.Utf8.parse(e.data.key);
var iv = CryptoJS.enc.Utf8.parse(e.data.iv);
// Create aes encryptor object
var aesEncryptor = CryptoJS.algo.AES.createEncryptor(key, { iv: iv, mode: CryptoJS.mode.CTR, padding: CryptoJS.pad.NoPadding });

while (startByte <= (bytes.length - 1)) {
// Set the end byte
endByte = startByte + e.data.chunkSize;
if (endByte > bytes.length - 1)
{
endByte = bytes.length - 1;
}

// Grab current set of bytes
var curBytes = bytes.subarray(startByte, endByte);
//var b64encoded = btoa(String.fromCharCode.apply(null, curBytes));
var wordArray = CryptoJS.lib.WordArray.create(curBytes)

// encrypt the passed in file data
var enc = aesEncryptor.process(wordArray);

// Convert and add to current array buffer
var encStr = enc.toString(CryptoJS.enc.Base64); // to string
prog.pushArray(_base64ToArray(encStr));

// Send an update on progress
var objData =
{
cmd: 'progress',
processed: endByte,
total: bytes.length - 1
};

self.postMessage(objData);

// Set the next start as the current end
startByte = endByte + 1;
}

//then finalize
var encFinal = aesEncryptor.finalize();
var finalStr = encFinal.toString(CryptoJS.enc.Base64); // to final string
prog.pushArray(_base64ToArray(finalStr));

var objData =
{
cmd: 'progress',
processed: bytes.length - 1,
total: bytes.length - 1
};

// convert array to ArrayBuffer
var arBuf = _arrayToArrayBuffer(prog);

//throw JSON.stringify({ dataLength: prog.length, len: bytes.length, finalLength: arBuf.byteLength })

// Now package it into a mesage to send home
var objData =
{
cmd: 'finish',
encrypted: arBuf
};

self.postMessage(objData, [objData.encrypted]);
aesCrypto = CryptoJS.algo.AES.createEncryptor(key, { iv: iv, mode: CryptoJS.mode.CTR, padding: CryptoJS.pad.NoPadding });
break;
case 'decrypt':
// decrypt the passed in file data
var decrypted = CryptoJS.AES.decrypt(e.data.file, e.data.key, { iv: e.data.iv });

var fileText = decrypted.toString();

self.postMessage(fileText);
aesCrypto = CryptoJS.algo.AES.createDecryptor(key, { iv: iv, mode: CryptoJS.mode.CTR, padding: CryptoJS.pad.NoPadding });
break;
default:
break;
}

while (startByte <= (bytes.length - 1)) {
// Set the end byte
endByte = startByte + e.data.chunkSize;
if (endByte > bytes.length - 1) {
endByte = bytes.length - 1;
}

// Grab current set of bytes
var curBytes = bytes.subarray(startByte, endByte);
//var b64encoded = btoa(String.fromCharCode.apply(null, curBytes));
var wordArray = CryptoJS.lib.WordArray.create(curBytes)

// encrypt the passed in file data
var enc = aesCrypto.process(wordArray);

// Convert and add to current array buffer
var encStr = enc.toString(CryptoJS.enc.Base64); // to string
prog.pushArray(_base64ToArray(encStr));

// Send an update on progress
var objData =
{
cmd: 'progress',
processed: endByte,
total: bytes.length - 1
};

self.postMessage(objData);

// Set the next start as the current end
startByte = endByte + 1;
}

//then finalize
var encFinal = aesCrypto.finalize();
var finalStr = encFinal.toString(CryptoJS.enc.Base64); // to final string
prog.pushArray(_base64ToArray(finalStr));

var objData =
{
cmd: 'progress',
processed: bytes.length - 1,
total: bytes.length - 1
};

// convert array to ArrayBuffer
var arBuf = _arrayToArrayBuffer(prog);

//throw JSON.stringify({ dataLength: prog.length, len: bytes.length, finalLength: arBuf.byteLength })

// Now package it into a mesage to send home
var objData =
{
cmd: 'finish',
buffer: arBuf
};

self.postMessage(objData, [objData.buffer]);
}, false);

function _arrayToArrayBuffer(array) {

+ 61
- 9
Teknik/Areas/Upload/Scripts/Upload.js View File

@@ -53,6 +53,13 @@ function linkRemove(selector, fileID) {
});
}

function linkCancel(selector, fileID) {
$(selector).click(function () {
$('#link-' + fileID).remove();
return false;
});
}

var fileCount = 0;

var dropZone = new Dropzone(document.body, {
@@ -84,11 +91,31 @@ var dropZone = new Dropzone(document.body, {
</div> \
</div> \
</div> \
<div class="panel-footer" id="link-footer-' + fileID + '"> \
<div class="row"> \
<div class="col-sm-12 text-center"> \
<button type="button" class="btn btn-default btn-sm" id="remove-link-' + fileID + '">Remove From List</button> \
</div> \
</div> \
</div> \
</div> \
');

// Encrypt the file
encryptFile(file, uploadFile);
linkRemove('#remove-link-' + fileID + '', fileID);

// Check the file size
if (file.size <= maxUploadSize) {
// Encrypt the file and upload it
encryptFile(file, uploadFile);
}
else
{
// An error occured
$("#progress-" + fileID).children('.progress-bar').css('width', '100%');
$("#progress-" + fileID).children('.progress-bar').removeClass('progress-bar-success');
$("#progress-" + fileID).children('.progress-bar').addClass('progress-bar-danger');
$("#progress-" + fileID).children('.progress-bar').html('File Too Large');
}
this.removeFile(file);
}
});
@@ -108,6 +135,13 @@ function encryptFile(file, callback) {
var keyStr = randomString(24, '#aA');
var ivStr = randomString(24, '#aA');

// Let's grab the header and get the file type
var arr = (new Uint8Array(e.target.result)).subarray(0, 4);
var header = "";
for (var i = 0; i < arr.length; i++) {
header += arr[i].toString(16);
}

var worker = new Worker(encScriptSrc);

worker.addEventListener('message', function (e) {
@@ -121,7 +155,7 @@ function encryptFile(file, callback) {
case 'finish':
if (callback != null) {
// Finish
callback(e.data.encrypted, keyStr, ivStr, filetype, fileID);
callback(e.data.buffer, keyStr, ivStr, filetype, fileID);
}
break;
}
@@ -194,13 +228,10 @@ function uploadComplete(fileID, key, evt) {
obj = JSON.parse(evt.target.responseText);
var name = obj.result.name;
var fullName = decodeURIComponent(obj.result.url);
var keyVar = decodeURIComponent(obj.result.keyVar);
fullName = fullName.replace(keyVar, key);
$('#progress-' + fileID).children('.progress-bar').css('width', '100%');
$('#progress-' + fileID).children('.progress-bar').html('Complete');
$('#upload-link-' + fileID).html('<p><a href="' + fullName + '" target="_blank" class="alert-link">' + fullName + '</a></p>');
$('#link-' + fileID).append(' \
<div class="panel-footer"> \
$('#upload-link-' + fileID).html('<p><a href="' + fullName + '#' + key + '" target="_blank" class="alert-link">' + fullName + '#' + key + '</a></p>');
$('#link-footer-' + fileID).html(' \
<div class="row"> \
<div class="col-sm-4 text-center"> \
<button type="button" class="btn btn-default btn-sm" id="save-key-link-' + fileID + '">Save Key On Server</button> \
@@ -212,7 +243,6 @@ function uploadComplete(fileID, key, evt) {
<button type="button" class="btn btn-default btn-sm" id="remove-link-' + fileID + '">Remove From List</button> \
</div> \
</div> \
</div> \
');
linkSaveKey('#save-key-link-' + fileID + '', name, key, fileID);
linkUploadDelete('#generate-delete-link-' + fileID + '', name);
@@ -232,3 +262,25 @@ function uploadCanceled(fileID, evt) {
$("#progress-" + fileID).children('.progress-bar').addClass('progress-bar-warning');
$('#progress-' + fileID).children('.progress-bar').html('Upload Canceled');
}

function GetMIMEType(header)
{
var type = "";
switch (header) {
case "89504e47":
type = "image/png";
break;
case "47494638":
type = "image/gif";
break;
case "ffd8ffe0":
case "ffd8ffe1":
case "ffd8ffe2":
type = "image/jpeg";
break;
default:
type = "unknown"; // Or you can use the blob.type as fallback
break;
}
return type;
}

+ 3
- 9
Teknik/Areas/Upload/UploadAreaRegistration.cs View File

@@ -29,13 +29,6 @@ namespace Teknik.Areas.Upload
new { controller = "Upload", action = "Download", url = string.Empty },
new[] { typeof(Controllers.UploadController).Namespace }
);
context.MapSubdomainRoute(
"Upload.Download.Key",
"dev",
"Upload/{file}/{key}",
new { controller = "Upload", action = "Download", url = string.Empty },
new[] { typeof(Controllers.UploadController).Namespace }
);
context.MapSubdomainRoute(
"Upload.Delete",
"dev",
@@ -111,8 +104,9 @@ namespace Teknik.Areas.Upload
BundleTable.Bundles.Add(new ScriptBundle("~/bundles/upload").Include(
"~/Scripts/Dropzone/dropzone.js",
"~/Areas/Upload/Scripts/Upload.js",
"~/Scripts/bootbox/bootbox.min.js",
"~/Scripts/Crypto-js/aes.js"));
"~/Scripts/bootbox/bootbox.min.js"));
BundleTable.Bundles.Add(new ScriptBundle("~/bundles/download").Include(
"~/Areas/Upload/Scripts/Download.js"));
BundleTable.Bundles.Add(new ScriptBundle("~/bundles/cryptoWorker").Include(
"~/Areas/Upload/Scripts/EncryptionWorker.js"));
BundleTable.Bundles.Add(new ScriptBundle("~/bundles/crypto").Include(

+ 1
- 0
Teknik/Areas/Upload/Uploader.cs View File

@@ -50,6 +50,7 @@ namespace Teknik.Areas.Upload
upload.IV = iv;

db.Uploads.Add(upload);
db.SaveChanges();

return upload;
}

+ 16
- 0
Teknik/Areas/Upload/ViewModels/DownloadViewModel.cs View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Teknik.ViewModels;

namespace Teknik.Areas.Upload.ViewModels
{
public class DownloadViewModel : ViewModelBase
{
public string FileName { get; set; }
public string ContentType { get; set; }
public string Key { get; set; }
public string IV { get; set; }
}
}

+ 29
- 1
Teknik/Areas/Upload/Views/Upload/Download.cshtml View File

@@ -1 +1,29 @@
@model Teknik.Areas.Upload.ViewModels.UploadViewModel
@model Teknik.Areas.Upload.ViewModels.DownloadViewModel

<script>
var encScriptSrc = '@Scripts.Url("~/bundles/cryptoWorker")';
var aesScriptSrc = '@Scripts.Url("~/bundles/crypto")';
var downloadDataUrl = '@Url.SubRouteUrl("upload", "Upload.Action", new { action = "DownloadData" })';
var fileName = '@Model.FileName';
var fileType = '@Model.ContentType';
var key = window.location.hash.substring(1);
if (key == null)
{
key = '@((Model.Key != null) ? Model.Key : string.Empty)';
}
var iv = '@Model.IV';
var chunkSize = @(Model.Config.UploadConfig.ChunkSize);
</script>

<!-- Add UI for downloading info -->
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="progress" id="progress">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 0%">0%</div>
</div>
</div>
</div>
</div>

@Scripts.Render("~/bundles/download")

+ 10
- 3
Teknik/Areas/Upload/Views/Upload/Index.cshtml View File

@@ -6,8 +6,8 @@
var generateDeleteKeyURL = '@Url.SubRouteUrl("upload", "Upload.Action", new { action= "GenerateDeleteKey" })';
var saveKeyToServerURL = '@Url.SubRouteUrl("upload", "Upload.Action", new { action= "SaveFileKey" })';
var uploadFileURL = '@Url.SubRouteUrl("upload", "Upload.Action", new { action = "Upload" })';
var maxUploadSize = @(Model.Config.UploadConfig.MaxUploadSize / 100000);
var chunkSize = @(Model.Config.UploadConfig.ChunkSize);
var maxUploadSize = @Model.Config.UploadConfig.MaxUploadSize;
var chunkSize = @Model.Config.UploadConfig.ChunkSize;
</script>

@Styles.Render("~/Content/upload")
@@ -47,7 +47,14 @@
<div class="container" id="upload-links">
</div>
<br />
<div class="well text-center">Each file is encrypted on upload using an AES-256-CTR cipher. If you wish to view the file decrypted, you must use the direct Teknik link.</div>
<div class="well text-center">
<p>
Each file is encrypted on upload using an AES-256-CTR cipher. If you wish to view the file decrypted, you must use the direct Teknik link.
</p>
<p>
The maximum file size per upload is <b>@Utility.GetBytesReadable(Model.Config.UploadConfig.MaxUploadSize)</b>
</p>
</div>
<div class="text-center">
Useful Tools: <a href="http://git.teknik.io/Teknikode/Tools/src/master/Upload">Upload Scripts and Utilities</a> | <a href="https://github.com/jschx/poomf">Poomf Uploader</a>
<br />

+ 46
- 0
Teknik/Helpers/Utility.cs View File

@@ -88,5 +88,51 @@ namespace Teknik

return result;
}
public static string GetBytesReadable(long i)
{
// Get absolute value
long absolute_i = (i < 0 ? -i : i);
// Determine the suffix and readable value
string suffix;
double readable;
if (absolute_i >= 0x1000000000000000) // Exabyte
{
suffix = "EB";
readable = (i >> 50);
}
else if (absolute_i >= 0x4000000000000) // Petabyte
{
suffix = "PB";
readable = (i >> 40);
}
else if (absolute_i >= 0x10000000000) // Terabyte
{
suffix = "TB";
readable = (i >> 30);
}
else if (absolute_i >= 0x40000000) // Gigabyte
{
suffix = "GB";
readable = (i >> 20);
}
else if (absolute_i >= 0x100000) // Megabyte
{
suffix = "MB";
readable = (i >> 10);
}
else if (absolute_i >= 0x400) // Kilobyte
{
suffix = "KB";
readable = i;
}
else
{
return i.ToString("0 B"); // Byte
}
// Divide by 1024 to get fractional value
readable = (readable / 1024);
// Return formatted number with suffix
return readable.ToString("0.### ") + suffix;
}
}
}

BIN
Teknik/Scripts/_references.js View File


+ 2
- 0
Teknik/Teknik.csproj View File

@@ -170,6 +170,7 @@
<Compile Include="Areas\Upload\Models\Upload.cs" />
<Compile Include="Areas\Upload\UploadAreaRegistration.cs" />
<Compile Include="Areas\Upload\Uploader.cs" />
<Compile Include="Areas\Upload\ViewModels\DownloadViewModel.cs" />
<Compile Include="Areas\Upload\ViewModels\UploadViewModel.cs" />
<Compile Include="Configuration\BlogConfig.cs" />
<Compile Include="Configuration\Config.cs" />
@@ -209,6 +210,7 @@
<Content Include="Areas\Home\Content\Home.css" />
<Content Include="Areas\Home\Scripts\Home.js" />
<Content Include="Areas\Profile\Scripts\Profile.js" />
<Content Include="Areas\Upload\Scripts\Download.js" />
<Content Include="Scripts\Crypto-js\aes.js" />
<Content Include="Areas\Upload\Scripts\EncryptionWorker.js" />
<Content Include="Areas\Upload\Scripts\Upload.js" />

+ 3
- 1
Teknik/Web.config View File

@@ -27,7 +27,9 @@
<modules>
<remove name="FormsAuthentication" />
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
<add name="PerfModule" type="Teknik.Modules.PerformanceMonitorModule, Teknik" />
<add name="PerfModule" type="Teknik.Modules.PerformanceMonitorModule, Teknik" />
<remove name="UrlRoutingModule-4.0" />
<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="" />
</modules>
<staticContent>
<mimeMap fileExtension="woff" mimeType="application/font-woff" />

Loading…
Cancel
Save