public IActionResult DownloadData(string file, bool decrypt) { if (_config.UploadConfig.DownloadEnabled) { Models.Upload upload = _dbContext.Uploads.Where(up => up.Url == file).FirstOrDefault(); if (upload != null) { // Check Expiration if (UploadHelper.CheckExpiration(upload)) { DeleteFile(upload); return(Json(new { error = new { message = "File Does Not Exist" } })); } string subDir = upload.FileName[0].ToString(); string filePath = Path.Combine(_config.UploadConfig.UploadDirectory, subDir, upload.FileName); if (System.IO.File.Exists(filePath)) { // Notify the client the content length we'll be outputting Response.Headers.Add("Content-Length", upload.ContentLength.ToString()); // Create content disposition var cd = new System.Net.Mime.ContentDisposition { FileName = upload.Url, Inline = true }; // Set the content type of this response Response.Headers.Add("Content-Type", upload.ContentType); Response.Headers.Add("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 BufferedFileStreamResult(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 BufferedFileStreamResult(upload.ContentType, (response) => ResponseHelper.StreamToOutput(response, true, fs, (int)upload.ContentLength, _config.UploadConfig.ChunkSize), false)); } } } return(Json(new { error = new { message = "File Does Not Exist" } })); } return(Json(new { error = new { message = "Downloads are disabled" } })); }
public async Task <IActionResult> Download(string file) { if (_config.UploadConfig.DownloadEnabled) { ViewBag.Title = "Download " + file; string fileName = string.Empty; string url = string.Empty; string key = string.Empty; string iv = string.Empty; string contentType = string.Empty; long contentLength = 0; bool premiumAccount = false; DateTime dateUploaded = new DateTime(); Models.Upload upload = _dbContext.Uploads.Where(up => up.Url == file).FirstOrDefault(); if (upload != null) { // Check Expiration if (UploadHelper.CheckExpiration(upload)) { DeleteFile(upload); return(new StatusCodeResult(StatusCodes.Status404NotFound)); } upload.Downloads += 1; _dbContext.Entry(upload).State = EntityState.Modified; _dbContext.SaveChanges(); fileName = upload.FileName; url = upload.Url; key = upload.Key; iv = upload.IV; contentType = upload.ContentType; contentLength = upload.ContentLength; dateUploaded = upload.DateUploaded; if (User.Identity.IsAuthenticated) { IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); premiumAccount = userInfo.AccountType == AccountType.Premium; } if (!premiumAccount && upload.User != null) { IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, upload.User.Username); premiumAccount = userInfo.AccountType == AccountType.Premium; } } else { return(new StatusCodeResult(StatusCodes.Status404NotFound)); } // We don't have the key, so we need to decrypt it client side if (string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(iv)) { DownloadViewModel model = new DownloadViewModel(); model.CurrentSub = Subdomain; model.FileName = file; model.ContentType = contentType; model.ContentLength = contentLength; model.IV = iv; model.Decrypt = true; return(View(model)); } else if (!premiumAccount && _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)); } else // We have the key, so that means server side decryption { // Check for the cache bool isCached = false; string modifiedSince = Request.Headers["If-Modified-Since"]; if (!string.IsNullOrEmpty(modifiedSince)) { DateTime modTime = new DateTime(); bool parsed = DateTime.TryParse(modifiedSince, out modTime); if (parsed) { if ((modTime - dateUploaded).TotalSeconds <= 1) { isCached = true; } } } if (isCached) { return(new StatusCodeResult(StatusCodes.Status304NotModified)); } else { string subDir = fileName[0].ToString(); string filePath = Path.Combine(_config.UploadConfig.UploadDirectory, subDir, fileName); long startByte = 0; long endByte = contentLength - 1; long length = contentLength; if (System.IO.File.Exists(filePath)) { #region Range Calculation // Are they downloading it by range? bool byRange = !string.IsNullOrEmpty(Request.Headers["Range"]); // We do not support ranges // check to see if we need to pass a specified range if (byRange) { long anotherStart = startByte; long anotherEnd = endByte; string[] arr_split = Request.Headers["Range"].ToString().Split(new char[] { '=' }); string range = arr_split[1]; // Make sure the client hasn't sent us a multibyte range if (range.IndexOf(",") > -1) { // (?) Shoud this be issued here, or should the first // range be used? Or should the header be ignored and // we output the whole content? Response.Headers.Add("Content-Range", "bytes " + startByte + "-" + endByte + "/" + contentLength); return(new StatusCodeResult(StatusCodes.Status416RequestedRangeNotSatisfiable)); } // If the range starts with an '-' we start from the beginning // If not, we forward the file pointer // And make sure to get the end byte if spesified if (range.StartsWith("-")) { // The n-number of the last bytes is requested anotherStart = startByte - Convert.ToInt64(range.Substring(1)); } else { arr_split = range.Split(new char[] { '-' }); anotherStart = Convert.ToInt64(arr_split[0]); long temp = 0; anotherEnd = (arr_split.Length > 1 && Int64.TryParse(arr_split[1].ToString(), out temp)) ? Convert.ToInt64(arr_split[1]) : contentLength; } /* Check the range and make sure it's treated according to the specs. * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html */ // End bytes can not be larger than $end. anotherEnd = (anotherEnd > endByte) ? endByte : anotherEnd; // Validate the requested range and return an error if it's not correct. if (anotherStart > anotherEnd || anotherStart > contentLength - 1 || anotherEnd >= contentLength) { Response.Headers.Add("Content-Range", "bytes " + startByte + "-" + endByte + "/" + contentLength); return(new StatusCodeResult(StatusCodes.Status416RequestedRangeNotSatisfiable)); } startByte = anotherStart; endByte = anotherEnd; length = endByte - startByte + 1; // Calculate new content length // Ranges are response of 206 Response.StatusCode = 206; } #endregion // Set Last Modified Response.GetTypedHeaders().LastModified = dateUploaded; // We accept ranges Response.Headers.Add("Accept-Ranges", "0-" + contentLength); // Notify the client the byte range we'll be outputting Response.Headers.Add("Content-Range", "bytes " + startByte + "-" + endByte + "/" + contentLength); // Notify the client the content length we'll be outputting Response.Headers.Add("Content-Length", length.ToString()); // Set the content type of this response Response.Headers.Add("Content-Type", contentType); // Create content disposition var cd = new System.Net.Mime.ContentDisposition { FileName = url, Inline = true }; Response.Headers.Add("Content-Disposition", cd.ToString()); // Read in the file FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); // Reset file stream to starting position (or start of range) fs.Seek(startByte, SeekOrigin.Begin); try { // If the IV is set, and Key is set, then decrypt it while sending if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(iv)) { byte[] keyBytes = Encoding.UTF8.GetBytes(key); byte[] ivBytes = Encoding.UTF8.GetBytes(iv); return(new BufferedFileStreamResult(contentType, async(response) => await ResponseHelper.StreamToOutput(response, true, new AesCounterStream(fs, false, keyBytes, ivBytes), (int)length, _config.UploadConfig.ChunkSize), false)); } else // Otherwise just send it { // Send the file return(new BufferedFileStreamResult(contentType, async(response) => await ResponseHelper.StreamToOutput(response, true, fs, (int)length, _config.UploadConfig.ChunkSize), false)); } } catch (Exception ex) { _logger.LogWarning(ex, "Error in Download: {url}", new { url }); } } } return(new StatusCodeResult(StatusCodes.Status404NotFound)); } } return(new StatusCodeResult(StatusCodes.Status403Forbidden)); }