- Added whitelist site setting for allowed file extensions to upload via media module.

- Hardcoded blacklist: web.config
- Superuser immune to whitelist restriction
- Zip files still allowed even if not in the list since these are expanded by the media module to allow for multi upload.
- Files within a zip must still pass white/black-list test per normal rules (file is skipped if not).

--HG--
branch : dev
This commit is contained in:
Dave Reed
2010-11-04 12:01:07 -07:00
parent e414469e0f
commit 79bec8cee6
10 changed files with 144 additions and 10 deletions

View File

@@ -3,10 +3,13 @@ using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Web.Mvc;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Media.Models;
using Orchard.Media.Services;
using Orchard.Media.ViewModels;
using Orchard.Settings;
using Orchard.UI.Notify;
using Orchard.Utility.Extensions;
@@ -153,6 +156,15 @@ namespace Orchard.Media.Controllers {
if (!ModelState.IsValid)
return View(viewModel);
// first validate them all
foreach (string fileName in Request.Files) {
HttpPostedFileBase file = Request.Files[fileName];
if (!_mediaService.FileAllowed(file)) {
ModelState.AddModelError("File", T("That file type is not allowed.").ToString());
return View(viewModel);
}
}
// then save them
foreach (string fileName in Request.Files) {
HttpPostedFileBase file = Request.Files[fileName];
_mediaService.UploadMediaFile(viewModel.MediaPath, file);

View File

@@ -0,0 +1,15 @@
using JetBrains.Annotations;
using Orchard.Data;
using Orchard.ContentManagement.Handlers;
using Orchard.Media.Models;
namespace Orchard.Media.Handlers {
[UsedImplicitly]
public class MediaSettingsPartHandler : ContentHandler {
public MediaSettingsPartHandler(IRepository<MediaSettingsPartRecord> repository) {
Filters.Add(new ActivatingFilter<MediaSettingsPart>("Site"));
Filters.Add(StorageFilter.For(repository));
Filters.Add(new TemplateFilterForRecord<MediaSettingsPartRecord>("MediaSettings", "Parts/Media.MediaSettings"));
}
}
}

View File

@@ -0,0 +1,16 @@
using Orchard.Data.Migration;
using Orchard.Media.Models;
namespace Orchard.Media {
public class MediaDataMigration : DataMigrationImpl {
public int Create() {
SchemaBuilder.CreateTable("MediaSettingsPartRecord",
table => table
.ContentPartRecord()
.Column<string>("UploadAllowedFileTypeWhitelist", c => c.WithDefault(MediaSettingsPartRecord.DefaultWhitelist).WithLength(255))
);
return 1;
}
}
}

View File

@@ -0,0 +1,11 @@
using Orchard.ContentManagement;
using System;
namespace Orchard.Media.Models {
public class MediaSettingsPart : ContentPart<MediaSettingsPartRecord> {
public string UploadAllowedFileTypeWhitelist {
get { return Record.UploadAllowedFileTypeWhitelist; }
set { Record.UploadAllowedFileTypeWhitelist = value; }
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Net.Mail;
using Orchard.ContentManagement.Records;
using System.ComponentModel.DataAnnotations;
namespace Orchard.Media.Models {
public class MediaSettingsPartRecord : ContentPartRecord {
internal const string DefaultWhitelist = "jpg jpeg gif png txt doc docx xls xlsx pdf ppt pptx pps ppsx odt ods odp";
private string _whitelist = DefaultWhitelist;
public virtual string UploadAllowedFileTypeWhitelist {
get { return _whitelist; }
set { _whitelist = value; }
}
}
}

View File

@@ -71,6 +71,10 @@
<ItemGroup>
<Compile Include="AdminMenu.cs" />
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Handlers\MediaSettingsPartHandler.cs" />
<Compile Include="Migrations.cs" />
<Compile Include="Models\MediaSettingsPart.cs" />
<Compile Include="Models\MediaSettingsPartRecord.cs" />
<Compile Include="ResourceManifest.cs" />
<Compile Include="Helpers\MediaHelpers.cs" />
<Compile Include="Permissions.cs" />
@@ -113,6 +117,9 @@
<Content Include="Views\Admin\EditProperties.cshtml" />
<Content Include="Views\Admin\Index.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\EditorTemplates\Parts\Media.MediaSettings.cshtml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@@ -13,5 +13,6 @@ namespace Orchard.Media.Services {
void DeleteFile(string name, string folderName);
void RenameFile(string name, string newName, string folderName);
string UploadMediaFile(string folderName, HttpPostedFileBase postedFile);
bool FileAllowed(HttpPostedFileBase postedFile);
}
}

View File

@@ -5,9 +5,12 @@ using System.Text;
using System.Web;
using ICSharpCode.SharpZipLib.Zip;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.FileSystems.Media;
using Orchard.Logging;
using Orchard.Media.Models;
using Orchard.Security;
using Orchard.Settings;
namespace Orchard.Media.Services {
[UsedImplicitly]
@@ -21,6 +24,9 @@ namespace Orchard.Media.Services {
}
public ILogger Logger { get; set; }
protected virtual ISite CurrentSite { get; [UsedImplicitly] private set; }
protected virtual IUser CurrentUser { get; [UsedImplicitly] private set; }
public string GetPublicUrl(string path) {
return _storageProvider.GetPublicUrl(path);
@@ -82,18 +88,18 @@ namespace Orchard.Media.Services {
}
public void RenameFile(string name, string newName, string folderName) {
_storageProvider.RenameFile(folderName + "\\" + name, folderName + "\\" + newName);
if (FileAllowed(newName, false)) {
_storageProvider.RenameFile(folderName + "\\" + name, folderName + "\\" + newName);
}
}
public string UploadMediaFile(string folderName, HttpPostedFileBase postedFile) {
if (postedFile.FileName.EndsWith(".zip")) {
UnzipMediaFileArchive(folderName, postedFile);
// Don't save the zip file.
return _storageProvider.GetPublicUrl(folderName);
}
if (postedFile.ContentLength > 0) {
if (FileAllowed(postedFile) && postedFile.ContentLength > 0) {
var filePath = Path.Combine(folderName, Path.GetFileName(postedFile.FileName));
var inputStream = postedFile.InputStream;
@@ -104,6 +110,41 @@ namespace Orchard.Media.Services {
return null;
}
private bool FileAllowed(string name, bool allowZip) {
if (string.IsNullOrWhiteSpace(name)) {
return false;
}
var mediaSettings = CurrentSite.As<MediaSettingsPart>();
var allowedExtensions = mediaSettings.UploadAllowedFileTypeWhitelist.ToLowerInvariant().Split(' ');
var ext = (Path.GetExtension(name) ?? "").TrimStart('.').ToLowerInvariant();
if (string.IsNullOrWhiteSpace(ext)) {
return false;
}
// whitelist does not apply to the superuser
if (CurrentUser == null || !CurrentSite.SuperUser.Equals(CurrentUser.UserName, StringComparison.Ordinal)) {
// zip files at the top level are allowed since this is how you upload multiple files at once.
if (allowZip && ext.Equals("zip", StringComparison.OrdinalIgnoreCase)) {
return true;
}
// must be in the whitelist
if (Array.IndexOf(allowedExtensions, ext) == -1) {
return false;
}
}
// blacklist always applies
if (string.Equals(name.Trim(), "web.config", StringComparison.OrdinalIgnoreCase)) {
return false;
}
return true;
}
public bool FileAllowed(HttpPostedFileBase postedFile) {
if (postedFile == null) {
return false;
}
return FileAllowed(postedFile.FileName, true);
}
private void SaveStream(string filePath, Stream inputStream) {
var file = _storageProvider.CreateFile(filePath);
var outputStream = file.OpenWrite();
@@ -139,12 +180,17 @@ namespace Orchard.Media.Services {
var entryName = Path.Combine(targetFolder, entry.Name);
var directoryName = Path.GetDirectoryName(entryName);
try { _storageProvider.CreateFolder(directoryName); }
catch {
// no handling needed - this is to force the folder to exist if it doesn't
}
// skip disallowed files
if (FileAllowed(entry.Name, false)) {
try {
_storageProvider.CreateFolder(directoryName);
}
catch {
// no handling needed - this is to force the folder to exist if it doesn't
}
SaveStream(entryName, fileInflater);
SaveStream(entryName, fileInflater);
}
}
}
}

View File

@@ -1,4 +1,6 @@
namespace Orchard.Media.ViewModels {
using Orchard.Media.Models;
namespace Orchard.Media.ViewModels {
public class MediaItemAddViewModel {
public string FolderName { get; set; }
public string MediaPath { get; set; }

View File

@@ -0,0 +1,9 @@
@model Orchard.Media.Models.MediaSettingsPartRecord
<fieldset>
<legend>@T("Media Settings")</legend>
<div>
@Html.LabelFor(m => m.UploadAllowedFileTypeWhitelist, T("Upload allowed file types (list of extensions separated by spaces)"))
@Html.TextBoxFor(m => m.UploadAllowedFileTypeWhitelist, new { @class = "textMedium" })
</div>
</fieldset>