Fixes up security issues with MediaUploadController, removes Delete since we shouldn't be arbitrarily deleting any media files without referencing media items, fixes up disposables and naming conventions.
This commit is contained in:
@@ -7,12 +7,15 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using System.Web.Http;
|
using System.Web.Http;
|
||||||
using System.Web.Script.Serialization;
|
using System.Web.Script.Serialization;
|
||||||
|
using Umbraco.Core;
|
||||||
using Umbraco.Web.Mvc;
|
using Umbraco.Web.Mvc;
|
||||||
using Umbraco.Web.WebApi;
|
using Umbraco.Web.WebApi;
|
||||||
using Umbraco.Web.WebApi.Filters;
|
using Umbraco.Web.WebApi.Filters;
|
||||||
|
using Constants = Umbraco.Core.Constants;
|
||||||
|
|
||||||
namespace Umbraco.Web.Editors
|
namespace Umbraco.Web.Editors
|
||||||
{
|
{
|
||||||
@@ -22,242 +25,265 @@ namespace Umbraco.Web.Editors
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
[PluginController("UmbracoApi")]
|
[PluginController("UmbracoApi")]
|
||||||
public class MediaUploadController : UmbracoApiController
|
[UmbracoApplicationAuthorizeAttribute(Constants.Applications.Media)]
|
||||||
|
public class MediaUploadController : UmbracoAuthorizedApiController
|
||||||
|
{
|
||||||
|
private readonly JavaScriptSerializer _js = new JavaScriptSerializer { MaxJsonLength = 41943040 };
|
||||||
|
private readonly string _storageRoot = Core.IO.IOHelper.MapPath(Core.IO.SystemDirectories.Media);
|
||||||
|
|
||||||
|
#region Get
|
||||||
|
private HttpResponseMessage DownloadFileContent()
|
||||||
{
|
{
|
||||||
private readonly JavaScriptSerializer _js = new JavaScriptSerializer { MaxJsonLength = 41943040 };
|
var filename = HttpContext.Current.Request["f"];
|
||||||
private readonly string _storageRoot = Core.IO.IOHelper.MapPath( Core.IO.SystemDirectories.Media );
|
var filePath = _storageRoot + filename;
|
||||||
public bool _isReusable { get { return false; } }
|
if (File.Exists(filePath))
|
||||||
|
|
||||||
|
|
||||||
public string Meh()
|
|
||||||
{
|
{
|
||||||
return "wo";
|
var response = new HttpResponseMessage(HttpStatusCode.OK);
|
||||||
}
|
response.Content = new StreamContent(new FileStream(filePath, FileMode.Open, FileAccess.Read));
|
||||||
|
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
||||||
|
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
|
||||||
#region Get
|
|
||||||
private HttpResponseMessage DownloadFileContent()
|
|
||||||
{
|
|
||||||
var filename = HttpContext.Current.Request["f"];
|
|
||||||
var filePath = _storageRoot + filename;
|
|
||||||
if (File.Exists(filePath))
|
|
||||||
{
|
{
|
||||||
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
|
FileName = filename
|
||||||
response.Content = new StreamContent(new FileStream(filePath, FileMode.Open, FileAccess.Read));
|
|
||||||
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
|
||||||
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
|
|
||||||
{
|
|
||||||
FileName = filename
|
|
||||||
};
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
return ControllerContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpResponseMessage DownloadFileList()
|
|
||||||
{
|
|
||||||
var files =
|
|
||||||
new DirectoryInfo(_storageRoot)
|
|
||||||
.GetFiles("*", SearchOption.TopDirectoryOnly)
|
|
||||||
.Where(f => !f.Attributes.HasFlag(FileAttributes.Hidden))
|
|
||||||
.Select(f => new FilesStatus(f))
|
|
||||||
.ToArray();
|
|
||||||
HttpContext.Current.Response.AppendHeader("Content-Disposition", "inline; filename=\"files.json\"");
|
|
||||||
return ControllerContext.Request.CreateResponse(HttpStatusCode.OK, _js.Serialize(files));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public HttpResponseMessage Get()
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(HttpContext.Current.Request["f"]))
|
|
||||||
{
|
|
||||||
return DownloadFileContent();
|
|
||||||
}
|
|
||||||
return DownloadFileList();
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Post & Put
|
|
||||||
public HttpResponseMessage Post()
|
|
||||||
{
|
|
||||||
return UploadFile(HttpContext.Current);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpResponseMessage Put()
|
|
||||||
{
|
|
||||||
return UploadFile(HttpContext.Current);
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpResponseMessage UploadFile(HttpContext context)
|
|
||||||
{
|
|
||||||
var statuses = new List<FilesStatus>();
|
|
||||||
var headers = context.Request.Headers;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(headers["X-File-Name"]))
|
|
||||||
{
|
|
||||||
UploadWholeFile(context, statuses);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UploadPartialFile(headers["X-File-Name"], context, statuses);
|
|
||||||
}
|
|
||||||
|
|
||||||
return WriteJsonIframeSafe(context, statuses);
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpResponseMessage WriteJsonIframeSafe(HttpContext context, List<FilesStatus> statuses)
|
|
||||||
{
|
|
||||||
context.Response.AddHeader("Vary", "Accept");
|
|
||||||
var response = new HttpResponseMessage()
|
|
||||||
{
|
|
||||||
Content = new StringContent(_js.Serialize(statuses.ToArray()))
|
|
||||||
};
|
};
|
||||||
if (context.Request["HTTP_ACCEPT"].Contains("application/json"))
|
|
||||||
{
|
|
||||||
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
|
|
||||||
}
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
return ControllerContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "");
|
||||||
// Upload partial file
|
|
||||||
private void UploadPartialFile(string fileName, HttpContext context, List<FilesStatus> statuses)
|
|
||||||
{
|
|
||||||
if (context.Request.Files.Count != 1) throw new HttpRequestValidationException("Attempt to upload chunked file containing more than one fragment per request");
|
|
||||||
var inputStream = context.Request.Files[0].InputStream;
|
|
||||||
var fullName = _storageRoot + Path.GetFileName(fileName);
|
|
||||||
|
|
||||||
using (var fs = new FileStream(fullName, FileMode.Append, FileAccess.Write))
|
|
||||||
{
|
|
||||||
var buffer = new byte[1024];
|
|
||||||
|
|
||||||
var l = inputStream.Read(buffer, 0, 1024);
|
|
||||||
while (l > 0)
|
|
||||||
{
|
|
||||||
fs.Write(buffer, 0, l);
|
|
||||||
l = inputStream.Read(buffer, 0, 1024);
|
|
||||||
}
|
|
||||||
fs.Flush();
|
|
||||||
fs.Close();
|
|
||||||
}
|
|
||||||
statuses.Add(new FilesStatus(new FileInfo(fullName)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload entire file
|
|
||||||
private void UploadWholeFile(HttpContext context, List<FilesStatus> statuses)
|
|
||||||
{
|
|
||||||
var folderId = context.Request.Form["currentFolder"];
|
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < context.Request.Files.Count; i++)
|
|
||||||
{
|
|
||||||
var file = context.Request.Files[i];
|
|
||||||
string fullPath = _storageRoot + Path.GetFileName(file.FileName);
|
|
||||||
Directory.CreateDirectory(_storageRoot);
|
|
||||||
|
|
||||||
file.SaveAs(fullPath);
|
|
||||||
string fullName = Path.GetFileName(file.FileName);
|
|
||||||
statuses.Add(new FilesStatus(fullName, file.ContentLength, fullPath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Delete
|
|
||||||
public HttpResponseMessage Delete()
|
|
||||||
{
|
|
||||||
var filePath = _storageRoot + HttpContext.Current.Request["f"];
|
|
||||||
if (File.Exists(filePath))
|
|
||||||
{
|
|
||||||
File.Delete(filePath);
|
|
||||||
}
|
|
||||||
return ControllerContext.Request.CreateResponse(HttpStatusCode.OK, "");
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region FileStatus
|
private HttpResponseMessage DownloadFileList()
|
||||||
public class FilesStatus
|
|
||||||
{
|
{
|
||||||
public const string HandlerPath = "/";
|
var files =
|
||||||
|
new DirectoryInfo(_storageRoot)
|
||||||
|
.GetFiles("*", SearchOption.TopDirectoryOnly)
|
||||||
|
.Where(f => f.Attributes.HasFlag(FileAttributes.Hidden) == false)
|
||||||
|
.Select(f => new FilesStatus(f))
|
||||||
|
.ToArray();
|
||||||
|
HttpContext.Current.Response.AppendHeader("Content-Disposition", "inline; filename=\"files.json\"");
|
||||||
|
return ControllerContext.Request.CreateResponse(HttpStatusCode.OK, _js.Serialize(files));
|
||||||
|
}
|
||||||
|
|
||||||
public string group { get; set; }
|
public HttpResponseMessage Get()
|
||||||
public string name { get; set; }
|
{
|
||||||
public string type { get; set; }
|
var http = TryGetHttpContext();
|
||||||
public int size { get; set; }
|
if (http.Success == false)
|
||||||
public string progress { get; set; }
|
|
||||||
public string url { get; set; }
|
|
||||||
public string thumbnail_url { get; set; }
|
|
||||||
public string delete_url { get; set; }
|
|
||||||
public string delete_type { get; set; }
|
|
||||||
public string error { get; set; }
|
|
||||||
|
|
||||||
public FilesStatus()
|
|
||||||
{
|
{
|
||||||
|
return Request.CreateResponse(HttpStatusCode.NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FilesStatus(FileInfo fileInfo)
|
if (string.IsNullOrEmpty(http.Result.Request["f"]) == false)
|
||||||
{
|
{
|
||||||
SetValues(fileInfo.Name, (int)fileInfo.Length, fileInfo.FullName);
|
return DownloadFileContent();
|
||||||
|
}
|
||||||
|
return DownloadFileList();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Post & Put
|
||||||
|
public HttpResponseMessage Post()
|
||||||
|
{
|
||||||
|
var http = TryGetHttpContext();
|
||||||
|
return http.Success == false
|
||||||
|
? Request.CreateResponse(HttpStatusCode.NotFound)
|
||||||
|
: UploadFile(http.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResponseMessage Put()
|
||||||
|
{
|
||||||
|
var http = TryGetHttpContext();
|
||||||
|
return http.Success == false
|
||||||
|
? Request.CreateResponse(HttpStatusCode.NotFound)
|
||||||
|
: UploadFile(http.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpResponseMessage UploadFile(HttpContextBase context)
|
||||||
|
{
|
||||||
|
var statuses = new List<FilesStatus>();
|
||||||
|
var headers = context.Request.Headers;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(headers["X-File-Name"]))
|
||||||
|
{
|
||||||
|
UploadWholeFile(context, statuses);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UploadPartialFile(headers["X-File-Name"], context, statuses);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FilesStatus(string fileName, int fileLength, string fullPath)
|
return WriteJsonIframeSafe(context, statuses);
|
||||||
{
|
}
|
||||||
SetValues(fileName, fileLength, fullPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetValues(string fileName, int fileLength, string fullPath)
|
private HttpResponseMessage WriteJsonIframeSafe(HttpContextBase context, List<FilesStatus> statuses)
|
||||||
|
{
|
||||||
|
context.Response.AddHeader("Vary", "Accept");
|
||||||
|
var response = new HttpResponseMessage()
|
||||||
{
|
{
|
||||||
name = fileName;
|
Content = new StringContent(_js.Serialize(statuses.ToArray()))
|
||||||
type = "image/png";
|
};
|
||||||
size = fileLength;
|
if (context.Request["HTTP_ACCEPT"].Contains("application/json"))
|
||||||
progress = "1.0";
|
{
|
||||||
url = HandlerPath + "api/Upload?f=" + fileName;
|
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||||
delete_url = HandlerPath + "api/Upload?f=" + fileName;
|
}
|
||||||
delete_type = "DELETE";
|
else
|
||||||
var ext = Path.GetExtension(fullPath);
|
{
|
||||||
var fileSize = ConvertBytesToMegabytes(new FileInfo(fullPath).Length);
|
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
|
||||||
if (fileSize > 3 || !IsImage(ext))
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload partial file
|
||||||
|
private void UploadPartialFile(string fileName, HttpContextBase context, List<FilesStatus> statuses)
|
||||||
|
{
|
||||||
|
if (context.Request.Files.Count != 1) throw new HttpRequestValidationException("Attempt to upload chunked file containing more than one fragment per request");
|
||||||
|
var inputStream = context.Request.Files[0].InputStream;
|
||||||
|
var fullName = _storageRoot + Path.GetFileName(fileName);
|
||||||
|
|
||||||
|
using (var fs = new FileStream(fullName, FileMode.Append, FileAccess.Write))
|
||||||
|
{
|
||||||
|
var buffer = new byte[1024];
|
||||||
|
|
||||||
|
var l = inputStream.Read(buffer, 0, 1024);
|
||||||
|
while (l > 0)
|
||||||
{
|
{
|
||||||
thumbnail_url = "/Content/img/generalFile.png";
|
fs.Write(buffer, 0, l);
|
||||||
}
|
l = inputStream.Read(buffer, 0, 1024);
|
||||||
else
|
|
||||||
{
|
|
||||||
thumbnail_url = @"data:image/png;base64," + EncodeFile(fullPath);
|
|
||||||
}
|
}
|
||||||
|
fs.Flush();
|
||||||
|
fs.Close();
|
||||||
}
|
}
|
||||||
|
statuses.Add(new FilesStatus(new FileInfo(fullName)));
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsImage(string ext)
|
// Upload entire file
|
||||||
{
|
private void UploadWholeFile(HttpContextBase context, List<FilesStatus> statuses)
|
||||||
return ext == ".gif" || ext == ".jpg" || ext == ".png";
|
{
|
||||||
}
|
var folderId = context.Request.Form["currentFolder"];
|
||||||
|
|
||||||
private string EncodeFile(string fileName)
|
|
||||||
{
|
|
||||||
byte[] bytes;
|
|
||||||
using (Image image = Image.FromFile(fileName))
|
|
||||||
{
|
|
||||||
var ratioX = (double)80 / image.Width;
|
|
||||||
var ratioY = (double)80 / image.Height;
|
|
||||||
var ratio = Math.Min(ratioX, ratioY);
|
|
||||||
var newWidth = (int)(image.Width * ratio);
|
|
||||||
var newHeight = (int)(image.Height * ratio);
|
|
||||||
var newImage = new Bitmap(newWidth, newHeight);
|
|
||||||
Graphics.FromImage(newImage).DrawImage(image, 0, 0, newWidth, newHeight);
|
|
||||||
ImageConverter converter = new ImageConverter();
|
|
||||||
bytes = (byte[])converter.ConvertTo(newImage, typeof(byte[]));
|
|
||||||
newImage.Dispose();
|
|
||||||
}
|
|
||||||
return Convert.ToBase64String(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double ConvertBytesToMegabytes(long bytes)
|
for (int i = 0; i < context.Request.Files.Count; i++)
|
||||||
{
|
{
|
||||||
return (bytes / 1024f) / 1024f;
|
var file = context.Request.Files[i];
|
||||||
|
string fullPath = _storageRoot + Path.GetFileName(file.FileName);
|
||||||
|
Directory.CreateDirectory(_storageRoot);
|
||||||
|
|
||||||
|
file.SaveAs(fullPath);
|
||||||
|
string fullName = Path.GetFileName(file.FileName);
|
||||||
|
statuses.Add(new FilesStatus(fullName, file.ContentLength, fullPath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#region FileStatus
|
||||||
|
|
||||||
|
[DataContract(Name = "fileStatus", Namespace = "")]
|
||||||
|
internal class FilesStatus
|
||||||
|
{
|
||||||
|
public const string HandlerPath = "/";
|
||||||
|
|
||||||
|
[DataMember(Name = "group")]
|
||||||
|
public string Group { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "size")]
|
||||||
|
public int Size { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "progress")]
|
||||||
|
public string Progress { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "url")]
|
||||||
|
public string Url { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "thumbnail_url")]
|
||||||
|
public string ThumbnailUrl { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "delete_url")]
|
||||||
|
public string DeleteUrl { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "delete_type")]
|
||||||
|
public string DeleteType { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "error")]
|
||||||
|
public string Error { get; set; }
|
||||||
|
|
||||||
|
public FilesStatus()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilesStatus(FileInfo fileInfo)
|
||||||
|
{
|
||||||
|
SetValues(fileInfo.Name, (int)fileInfo.Length, fileInfo.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilesStatus(string fileName, int fileLength, string fullPath)
|
||||||
|
{
|
||||||
|
SetValues(fileName, fileLength, fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetValues(string fileName, int fileLength, string fullPath)
|
||||||
|
{
|
||||||
|
Name = fileName;
|
||||||
|
Type = "image/png";
|
||||||
|
Size = fileLength;
|
||||||
|
Progress = "1.0";
|
||||||
|
Url = HandlerPath + "api/Upload?f=" + fileName;
|
||||||
|
DeleteUrl = HandlerPath + "api/Upload?f=" + fileName;
|
||||||
|
DeleteType = "DELETE";
|
||||||
|
var ext = Path.GetExtension(fullPath);
|
||||||
|
var fileSize = ConvertBytesToMegabytes(new FileInfo(fullPath).Length);
|
||||||
|
if (fileSize > 3 || !IsImage(ext))
|
||||||
|
{
|
||||||
|
ThumbnailUrl = "/Content/img/generalFile.png";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ThumbnailUrl = @"data:image/png;base64," + EncodeFile(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsImage(string ext)
|
||||||
|
{
|
||||||
|
return ext.InvariantEquals(".gif") || ext.InvariantEquals(".jpg") || ext.InvariantEquals(".png");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EncodeFile(string fileName)
|
||||||
|
{
|
||||||
|
byte[] bytes = null;
|
||||||
|
using (var image = Image.FromFile(fileName))
|
||||||
|
{
|
||||||
|
var ratioX = (double)80 / image.Width;
|
||||||
|
var ratioY = (double)80 / image.Height;
|
||||||
|
var ratio = Math.Min(ratioX, ratioY);
|
||||||
|
var newWidth = (int)(image.Width * ratio);
|
||||||
|
var newHeight = (int)(image.Height * ratio);
|
||||||
|
using (var newImage = new Bitmap(newWidth, newHeight))
|
||||||
|
{
|
||||||
|
using (var g = Graphics.FromImage(newImage))
|
||||||
|
{
|
||||||
|
g.DrawImage(image, 0, 0, newWidth, newHeight);
|
||||||
|
var converter = new ImageConverter();
|
||||||
|
bytes = (byte[])converter.ConvertTo(newImage, typeof(byte[]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (bytes == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Could not read the bytes from the file as an image");
|
||||||
|
}
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double ConvertBytesToMegabytes(long bytes)
|
||||||
|
{
|
||||||
|
return (bytes / 1024f) / 1024f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user