#17280: Limiting access to media folder.

--HG--
branch : dev
This commit is contained in:
Andre Rodrigues
2011-01-31 12:12:22 -08:00
parent 1372fe250d
commit fa25bca5f5
17 changed files with 1125 additions and 232 deletions

View File

@@ -22,3 +22,12 @@ Scenario: Creating a folder
Then I should see "Manage Media Folders"
And I should see "Hello World"
And the status should be 200 "OK"
Scenario: Limited access
Given I have installed Orchard
And I have installed "Orchard.Media"
When I go to "admin/media/edit?name=..\..\bin&mediaPath=..\..\bin"
And I am redirected
Then I should see "Manage Media Folders"
And I should see "Editing failed: Invalid path"
And the status should be 200 "OK"

View File

@@ -1,7 +1,7 @@
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by SpecFlow (http://www.specflow.org/).
// SpecFlow Version:1.3.2.0
// SpecFlow Version:1.5.0.0
// Runtime Version:4.0.30319.1
//
// Changes to this file may cause incorrect behavior and will be lost if
@@ -14,7 +14,7 @@ namespace Orchard.Specs
using TechTalk.SpecFlow;
[System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.3.2.0")]
[System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.5.0.0")]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[NUnit.Framework.TestFixtureAttribute()]
[NUnit.Framework.DescriptionAttribute("Media management")]
@@ -31,7 +31,7 @@ namespace Orchard.Specs
{
testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner();
TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Media management", "In order to reference images and such from content\r\nAs an author\r\nI want to uploa" +
"d and manage files in a media folder", ((string[])(null)));
"d and manage files in a media folder", GenerationTargetLanguage.CSharp, ((string[])(null)));
testRunner.OnFeatureStart(featureInfo);
}
@@ -106,6 +106,31 @@ testRunner.Then("I should see \"Manage Media Folders\"");
testRunner.And("I should see \"Hello World\"");
#line 24
testRunner.And("the status should be 200 \"OK\"");
#line hidden
testRunner.CollectScenarioErrors();
}
[NUnit.Framework.TestAttribute()]
[NUnit.Framework.DescriptionAttribute("Limited access")]
public virtual void LimitedAccess()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Limited access", ((string[])(null)));
#line 26
this.ScenarioSetup(scenarioInfo);
#line 27
testRunner.Given("I have installed Orchard");
#line 28
testRunner.And("I have installed \"Orchard.Media\"");
#line 29
testRunner.When("I go to \"admin/media/edit?name=..\\..\\bin&mediaPath=..\\..\\bin\"");
#line 30
testRunner.And("I am redirected");
#line 31
testRunner.Then("I should see \"Manage Media Folders\"");
#line 32
testRunner.And("I should see \"Editing failed: Invalid path\"");
#line 33
testRunner.And("the status should be 200 \"OK\"");
#line hidden
testRunner.CollectScenarioErrors();
}

View File

@@ -0,0 +1,297 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ICSharpCode.SharpZipLib.Zip;
using NUnit.Framework;
using Orchard.Environment.Configuration;
using Orchard.FileSystems.Media;
using Orchard.Media.Models;
using Orchard.Media.Services;
using Orchard.Tests.Stubs;
using Orchard.Tests.UI.Navigation;
namespace Orchard.Tests.Modules.Media.Services {
[TestFixture]
public class MediaServiceTests {
private const string FolderName1 = "Folder1";
private const string FolderName2 = "Folder2";
private const string FolderName3 = "Folder3";
private const string InnerDirectory = "MyDir";
private const string TextFileName = "File1.txt";
private const string PaddedTextFileName = " File2.txt";
private const string FinalDottedTextFileName = "file2.txt.";
private const string WebconfigFileName = "web.config";
private const string PaddedWebconfigFileName = " web.config";
private const string FinalDottedWebconfigFileName = "web.config.";
private const string DllFileName = "test.dll";
private const string ZipFileName = "test.zip";
private const string NoExtensionFileName = "test";
private const string MediaFolder = "Media";
private const string ShellSettingsName = "Default";
private StubOrchardServices OrchardServices { get; set; }
private StubStorageProvider StorageProvider { get; set; }
private MediaServiceAccessor MediaService { get; set; }
[SetUp]
public void Setup() {
OrchardServices = new StubOrchardServices();
StorageProvider = new StubStorageProvider(new ShellSettings { Name = ShellSettingsName });
MediaService = new MediaServiceAccessor(StorageProvider, OrchardServices);
}
[Test]
public void GetPublicUrlTests() {
Assert.That(() => MediaService.GetPublicUrl(null), Throws.InstanceOf(typeof(ArgumentException)), "null relative path is invalid");
Assert.That(MediaService.GetPublicUrl(TextFileName), Is.EqualTo(string.Format("/{0}/{1}/{2}", MediaFolder, ShellSettingsName, TextFileName)), "base path file");
Assert.That(MediaService.GetPublicUrl(string.Format("{0}/{1}", InnerDirectory, TextFileName)), Is.EqualTo(string.Format("/{0}/{1}/{2}/{3}", MediaFolder, ShellSettingsName, InnerDirectory, TextFileName)), "file within directory");
}
[Test]
public void GetMediaFoldersTest() {
StorageProvider.ListFoldersPredicate = path => {
return string.IsNullOrEmpty(path)
? new[] {new StubStorageFolder(FolderName1)}
: string.Equals(path, FolderName1)
? new[] {new StubStorageFolder(FolderName2), new StubStorageFolder(FolderName3)}
: new StubStorageFolder[] {};
};
IEnumerable<MediaFolder> mediaFolders = MediaService.GetMediaFolders(null);
Assert.That(mediaFolders.Count(), Is.EqualTo(1), "Root path only has 1 sub directory");
Assert.That(mediaFolders.FirstOrDefault(mediaFolder => mediaFolder.Name.Equals(FolderName1)), Is.Not.Null, "Correct sub directory in root path");
mediaFolders = MediaService.GetMediaFolders(FolderName3);
Assert.That(mediaFolders.Count(), Is.EqualTo(0), "Invalid folder path has 0 sub directories");
mediaFolders = MediaService.GetMediaFolders(FolderName1);
Assert.That(mediaFolders.Count(), Is.EqualTo(2), "Folder1 has 2 sub directories");
Assert.That(mediaFolders.FirstOrDefault(mediaFolder => mediaFolder.Name.Equals(FolderName2)), Is.Not.Null, "Correct sub directory in root path");
Assert.That(mediaFolders.FirstOrDefault(mediaFolder => mediaFolder.Name.Equals(FolderName3)), Is.Not.Null, "Correct sub directory in root path");
}
[Test]
public void UnzipMediaFileArchiveNotNullParametersTest() {
// Test basic parameter validation
Assert.That(() => MediaService.UnzipMediaFileArchiveAccessor(null, new MemoryStream()), Throws.InstanceOf(typeof(ArgumentException)));
Assert.That(() => MediaService.UnzipMediaFileArchiveAccessor(FolderName1, null), Throws.InstanceOf(typeof(ArgumentException)));
}
[Test]
public void UnzipMediaFileArchiveAdministratorTest() {
// Test unzip some valid and invalid files as an administrator user
StorageProvider.SavedStreams.Clear();
StubWorkContextAccessor.WorkContextImpl.StubSite.DefaultSuperUser = OrchardServices.WorkContext.CurrentUser.UserName;
MediaService.UnzipMediaFileArchiveAccessor(FolderName1, CreateZipMemoryStream());
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, TextFileName)), Is.True, "text files are allowed for super users");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, PaddedTextFileName)), Is.True, "padded text files are allowed for super users");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, DllFileName)), Is.True, "dll files are allowed for super users");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, ZipFileName)), Is.False, "Recursive zip archive files are not allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, WebconfigFileName)), Is.False, "web.config files are never allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, NoExtensionFileName)), Is.False, "no extension files are never allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, FinalDottedWebconfigFileName)), Is.False, "no extension files are never allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, PaddedWebconfigFileName)), Is.False, "no extension files are never allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, FinalDottedTextFileName)), Is.False, "no extension files are never allowed");
Assert.That(StorageProvider.SavedStreams.Count, Is.EqualTo(3));
}
[Test]
public void UnzipMediaFileArchiveNonAdministratorNoWhitelistTest() {
// Test unzip some files as a non administrator user and without a white list (everything should be rejected by default)
StorageProvider.SavedStreams.Clear();
StubWorkContextAccessor.WorkContextImpl.StubSite.DefaultSuperUser = "myuser";
MediaService.UnzipMediaFileArchiveAccessor(FolderName1, CreateZipMemoryStream());
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, TextFileName)), Is.False, "text files are not allowed by default for non super users");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, DllFileName)), Is.False, "dll files are not allowed by default for non super users");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, ZipFileName)), Is.False, "Recursive zip archive files are not allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, WebconfigFileName)), Is.False, "web.config files are never allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, NoExtensionFileName)), Is.False, "no extension files are never allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, FinalDottedWebconfigFileName)), Is.False, "no extension files are never allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, PaddedWebconfigFileName)), Is.False, "no extension files are never allowed");
Assert.That(StorageProvider.SavedStreams.Count, Is.EqualTo(0));
}
[Test]
public void UnzipMediaFileArchiveNonAdministratorWhitelistTest() {
// Test unzip some files as a non administrator user but with a white list
StorageProvider.SavedStreams.Clear();
StubWorkContextAccessor.WorkContextImpl.StubSite.DefaultSuperUser = "myuser";
MediaSettingsPart mediaSettingsPart = new MediaSettingsPart {
Record = new MediaSettingsPartRecord { UploadAllowedFileTypeWhitelist = "txt dll config" }
};
StubWorkContextAccessor.WorkContextImpl.InitMethod = workContext => {
workContext.CurrentSite.ContentItem.Weld(mediaSettingsPart);
};
MediaService.UnzipMediaFileArchiveAccessor(FolderName1, CreateZipMemoryStream());
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, TextFileName)), Is.True, "text files are allowed by the white list for non super users");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, PaddedTextFileName)), Is.True, "padded text files are allowed for super users");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, DllFileName)), Is.True, "dll files are allowed by the white list for non super users");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, ZipFileName)), Is.False, "Recursive zip archive files are not allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, WebconfigFileName)), Is.False, "web.config files are never allowed even if config extensions are");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, NoExtensionFileName)), Is.False, "no extension files are never allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, FinalDottedWebconfigFileName)), Is.False, "no extension files are never allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, FinalDottedTextFileName)), Is.False, "no extension files are never allowed");
Assert.That(StorageProvider.SavedStreams.Contains(StorageProvider.Combine(FolderName1, PaddedWebconfigFileName)), Is.False, "no extension files are never allowed");
Assert.That(StorageProvider.SavedStreams.Count, Is.EqualTo(3));
}
private MemoryStream CreateZipMemoryStream() {
// Setup memory stream with zip archive for more complex scenarios
MemoryStream memoryStream = new MemoryStream();
ZipOutputStream zipOut = new ZipOutputStream(memoryStream);
zipOut.PutNextEntry(new ZipEntry(TextFileName));
zipOut.Write(new byte[] { 0x01 }, 0, 1);
zipOut.CloseEntry();
zipOut.PutNextEntry(new ZipEntry(WebconfigFileName));
zipOut.Write(new byte[] { 0x02 }, 0, 1);
zipOut.CloseEntry();
zipOut.PutNextEntry(new ZipEntry(DllFileName));
zipOut.Write(new byte[] { 0x03 }, 0, 1);
zipOut.CloseEntry();
zipOut.PutNextEntry(new ZipEntry(ZipFileName));
zipOut.Write(new byte[] { 0x04 }, 0, 1);
zipOut.CloseEntry();
zipOut.PutNextEntry(new ZipEntry(NoExtensionFileName));
zipOut.Write(new byte[] { 0x05 }, 0, 1);
zipOut.CloseEntry();
zipOut.PutNextEntry(new ZipEntry(PaddedWebconfigFileName));
zipOut.Write(new byte[] { 0x06 }, 0, 1);
zipOut.CloseEntry();
zipOut.PutNextEntry(new ZipEntry(FinalDottedWebconfigFileName));
zipOut.Write(new byte[] { 0x07 }, 0, 1);
zipOut.CloseEntry();
zipOut.PutNextEntry(new ZipEntry(PaddedTextFileName));
zipOut.Write(new byte[] { 0x08 }, 0, 1);
zipOut.CloseEntry();
zipOut.PutNextEntry(new ZipEntry(FinalDottedTextFileName));
zipOut.Write(new byte[] { 0x09 }, 0, 1);
zipOut.CloseEntry();
zipOut.Close();
return new MemoryStream(memoryStream.ToArray());
}
private class MediaServiceAccessor : MediaService {
public MediaServiceAccessor(IStorageProvider storageProvider, IOrchardServices orchardServices)
: base (storageProvider, orchardServices) {}
public void UnzipMediaFileArchiveAccessor(string targetFolder, Stream zipStream) {
UnzipMediaFileArchive(targetFolder, zipStream);
}
}
private class StubStorageProvider : IStorageProvider {
private FileSystemStorageProvider FileSystemStorageProvider { get; set; }
public Func<string, IEnumerable<IStorageFolder>> ListFoldersPredicate { get; set; }
public List<string> SavedStreams { get; set; }
public StubStorageProvider(ShellSettings settings) {
FileSystemStorageProvider = new FileSystemStorageProvider(settings);
SavedStreams = new List<string>();
}
public string GetPublicUrl(string path) {
return FileSystemStorageProvider.GetPublicUrl(path);
}
public IStorageFile GetFile(string path) {
throw new NotImplementedException();
}
public IEnumerable<IStorageFile> ListFiles(string path) {
throw new NotImplementedException();
}
public IEnumerable<IStorageFolder> ListFolders(string path) {
return ListFoldersPredicate(path);
}
public bool TryCreateFolder(string path) {
return false;
}
public void CreateFolder(string path) {
}
public void DeleteFolder(string path) {
}
public void RenameFolder(string path, string newPath) {
}
public void DeleteFile(string path) {
}
public void RenameFile(string path, string newPath) {
}
public IStorageFile CreateFile(string path) {
throw new NotImplementedException();
}
public string Combine(string path1, string path2) {
return FileSystemStorageProvider.Combine(path1, path2);
}
public bool TrySaveStream(string path, Stream inputStream) {
try { SaveStream(path, inputStream); }
catch { return false; }
return true;
}
public void SaveStream(string path, Stream inputStream) {
SavedStreams.Add(path);
}
}
private class StubStorageFolder : IStorageFolder {
public string Path { get; set; }
public string Name { get; set; }
public StubStorageFolder(string name) {
Name = name;
}
public string GetPath() {
return Path;
}
public string GetName() {
return Name;
}
public long GetSize() {
return 0;
}
public DateTime GetLastUpdated() {
return DateTime.Now;
}
public IStorageFolder GetParent() {
return new StubStorageFolder("");
}
}
}
}

View File

@@ -72,6 +72,10 @@
<Reference Include="FluentPath">
<HintPath>..\..\lib\fluentpath\FluentPath.dll</HintPath>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=0.85.5.452, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\sharpziplib\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="IronRuby, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\dlr\IronRuby.dll</HintPath>
@@ -138,6 +142,7 @@
<Compile Include="Comments\Services\CommentServiceTests.cs" />
<Compile Include="Indexing\LuceneIndexProviderTests.cs" />
<Compile Include="Indexing\LuceneSearchBuilderTests.cs" />
<Compile Include="Media\Services\MediaServiceTests.cs" />
<Compile Include="Scripting.Dlr\EvaluatorTests.cs" />
<Compile Include="Scripting\EvaluatorTestsBase.cs" />
<Compile Include="Scripting\EvaluatorTests.cs" />

View File

@@ -253,6 +253,7 @@
<Compile Include="Mvc\Routes\UrlPrefixTests.cs" />
<Compile Include="Records\BigRecord.cs" />
<Compile Include="Security\DefaultEncryptionServiceTests.cs" />
<Compile Include="Storage\FileSystemStorageProviderTests.cs" />
<Compile Include="Stubs\InMemoryWebSiteFolder.cs" />
<Compile Include="Stubs\StubHttpContextAccessor.cs" />
<Compile Include="Stubs\StubWorkContextAccessor.cs" />
@@ -285,7 +286,6 @@
<Compile Include="UI\Notify\NotifierTests.cs" />
<Compile Include="UI\Notify\NotifyFilterTests.cs" />
<Compile Include="Services\ClockTests.cs" />
<Compile Include="Storage\FileSystemStorageProviderTests.cs" />
<Compile Include="Stubs\StubClock.cs" />
<Compile Include="Stubs\StubContainerProvider.cs" />
<Compile Include="FakeTests.cs" />

View File

@@ -15,6 +15,10 @@ namespace Orchard.Tests.Storage {
_folderPath = Path.Combine(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Media"), "Default");
_filePath = _folderPath + "\\testfile.txt";
if (Directory.Exists(_folderPath)) {
Directory.Delete(_folderPath, true);
}
Directory.CreateDirectory(_folderPath);
File.WriteAllText(_filePath, "testfile contents");
@@ -165,8 +169,149 @@ namespace Orchard.Tests.Storage {
Assert.That(GetFile(Path.Combine("SubFolder1", "testfile4.txt")), Is.Null);
Assert.That(GetFile(Path.Combine("SubFolder1", "testfile5.txt")), Is.Not.Null);
}
[Test]
public void GetFileFailsInInvalidPath() {
Assert.That(() => _storageProvider.GetFile(@"../InvalidFile.txt"), Throws.InstanceOf(typeof(ArgumentException)));
Assert.That(() => _storageProvider.GetFile(@"../../InvalidFile.txt"), Throws.InstanceOf(typeof(ArgumentException)));
// Valid get one level up within the storage provider domain
_storageProvider.CreateFile(@"test.txt");
Assert.That(_storageProvider.GetFile(@"test.txt"), Is.Not.Null);
Assert.That(_storageProvider.GetFile(@"SubFolder1\..\test.txt"), Is.Not.Null);
}
[Test]
public void ListFilesFailsInInvalidPath() {
Assert.That(() => _storageProvider.ListFiles(@"../InvalidFolder"), Throws.InstanceOf(typeof(ArgumentException)));
Assert.That(() => _storageProvider.ListFiles(@"../../InvalidFolder"), Throws.InstanceOf(typeof(ArgumentException)));
// Valid get one level up within the storage provider domain
Assert.That(_storageProvider.ListFiles(@"SubFolder1"), Is.Not.Null);
Assert.That(_storageProvider.ListFiles(@"SubFolder1\.."), Is.Not.Null);
}
[Test]
public void ListFoldersFailsInInvalidPath() {
Assert.That(() => _storageProvider.ListFolders(@"../InvalidFolder"), Throws.InstanceOf(typeof(ArgumentException)));
Assert.That(() => _storageProvider.ListFolders(@"../../InvalidFolder"), Throws.InstanceOf(typeof(ArgumentException)));
// Valid get one level up within the storage provider domain
Assert.That(_storageProvider.ListFolders(@"SubFolder1"), Is.Not.Null);
Assert.That(_storageProvider.ListFolders(@"SubFolder1\.."), Is.Not.Null);
}
[Test]
public void TryCreateFolderFailsInInvalidPath() {
Assert.That(_storageProvider.TryCreateFolder(@"../InvalidFolder1"), Is.False);
Assert.That(_storageProvider.TryCreateFolder(@"../../InvalidFolder1"), Is.False);
// Valid create one level up within the storage provider domain
Assert.That(_storageProvider.TryCreateFolder(@"SubFolder1\..\ValidFolder1"), Is.True);
}
[Test]
public void CreateFolderFailsInInvalidPath() {
Assert.That(() => _storageProvider.CreateFolder(@"../InvalidFolder1"), Throws.InstanceOf(typeof(ArgumentException)));
Assert.That(() => _storageProvider.CreateFolder(@"../../InvalidFolder1"), Throws.InstanceOf(typeof(ArgumentException)));
// Valid create one level up within the storage provider domain
_storageProvider.CreateFolder(@"SubFolder1\..\ValidFolder1");
Assert.That(GetFolder("ValidFolder1"), Is.Not.Null);
}
[Test]
public void DeleteFolderFailsInInvalidPath() {
Assert.That(() => _storageProvider.DeleteFolder(@"../InvalidFolder1"), Throws.InstanceOf(typeof(ArgumentException)));
Assert.That(() => _storageProvider.DeleteFolder(@"../../InvalidFolder1"), Throws.InstanceOf(typeof(ArgumentException)));
// Valid create one level up within the storage provider domain
Assert.That(GetFolder("SubFolder1"), Is.Not.Null);
_storageProvider.DeleteFolder(@"SubFolder1\..\SubFolder1");
Assert.That(GetFolder("SubFolder1"), Is.Null);
}
[Test]
public void RenameFolderFailsInInvalidPath() {
Assert.That(GetFolder(@"SubFolder1/SubSubFolder1"), Is.Not.Null);
Assert.That(() => _storageProvider.RenameFolder(@"SubFolder1", @"../SubSubFolder1"), Throws.InstanceOf(typeof(ArgumentException)));
Assert.That(() => _storageProvider.RenameFolder(@"SubFolder1", @"../../SubSubFolder1"), Throws.InstanceOf(typeof(ArgumentException)));
// Valid move one level up within the storage provider domain
_storageProvider.RenameFolder(@"SubFolder1\SubSubFolder1", @"SubFolder1\..\SubSubFolder1");
Assert.That(GetFolder("SubSubFolder1"), Is.Not.Null);
_storageProvider.CreateFolder(@"SubFolder1\SubSubFolder1\SubSubSubFolder1");
_storageProvider.RenameFolder(@"SubFolder1\SubSubFolder1\SubSubSubFolder1", @"SubFolder1\SubSubFolder1\..\SubSubSubFolder1");
Assert.That(GetFolder(@"SubFolder1\SubSubSubFolder1"), Is.Not.Null);
}
[Test]
public void DeleteFileFailsInInvalidPath() {
Assert.That(() => _storageProvider.DeleteFile(@"../test.txt"), Throws.InstanceOf(typeof(ArgumentException)));
Assert.That(() => _storageProvider.DeleteFile(@"../test.txt"), Throws.InstanceOf(typeof(ArgumentException)));
// Valid move one level up within the storage provider domain
_storageProvider.CreateFile(@"test.txt");
Assert.That(GetFile("test.txt"), Is.Not.Null);
_storageProvider.DeleteFile(@"test.txt");
Assert.That(GetFile("test.txt"), Is.Null);
_storageProvider.CreateFile(@"test.txt");
Assert.That(GetFile("test.txt"), Is.Not.Null);
_storageProvider.DeleteFile(@"SubFolder1\..\test.txt");
Assert.That(GetFile("test.txt"), Is.Null);
}
[Test]
public void RenameFileFailsInInvalidPath() {
Assert.That(() => _storageProvider.RenameFile(@"../test.txt", "invalid.txt"), Throws.InstanceOf(typeof(ArgumentException)));
Assert.That(() => _storageProvider.RenameFile(@"../test.txt", "invalid.txt"), Throws.InstanceOf(typeof(ArgumentException)));
// Valid move one level up within the storage provider domain
_storageProvider.CreateFile(@"test.txt");
Assert.That(GetFile("test.txt"), Is.Not.Null);
_storageProvider.RenameFile(@"test.txt", "newName.txt");
Assert.That(GetFile("newName.txt"), Is.Not.Null);
_storageProvider.RenameFile(@"SubFolder1\..\newName.txt", "newNewName.txt");
Assert.That(GetFile("newNewName.txt"), Is.Not.Null);
}
[Test]
public void CreateFileFailsInInvalidPath() {
Assert.That(() => _storageProvider.CreateFile(@"../InvalidFolder1.txt"), Throws.InstanceOf(typeof(ArgumentException)));
Assert.That(() => _storageProvider.CreateFile(@"../../InvalidFolder1.txt"), Throws.InstanceOf(typeof(ArgumentException)));
// Valid create one level up within the storage provider domain
_storageProvider.CreateFile(@"SubFolder1\..\ValidFolder1.txt");
Assert.That(GetFile("ValidFolder1.txt"), Is.Not.Null);
}
[Test]
public void SaveStreamFailsInInvalidPath() {
_storageProvider.CreateFile(@"test.txt");
using (Stream stream = GetFile("test.txt").OpenRead()) {
Assert.That(() => _storageProvider.SaveStream(@"../newTest.txt", stream), Throws.InstanceOf(typeof(ArgumentException)));
Assert.That(() => _storageProvider.SaveStream(@"../../newTest.txt", stream), Throws.InstanceOf(typeof(ArgumentException)));
// Valid create one level up within the storage provider domain
_storageProvider.SaveStream(@"SubFolder1\..\newTest.txt", stream);
Assert.That(GetFile("newTest.txt"), Is.Not.Null);
}
}
[Test]
public void TrySaveStreamFailsInInvalidPath() {
_storageProvider.CreateFile(@"test.txt");
using (Stream stream = GetFile("test.txt").OpenRead()) {
Assert.That(_storageProvider.TrySaveStream(@"../newTest.txt", stream), Is.False);
Assert.That(_storageProvider.TrySaveStream(@"../../newTest.txt", stream), Is.False);
// Valid create one level up within the storage provider domain
Assert.That(_storageProvider.TrySaveStream(@"SubFolder1\..\newTest.txt", stream), Is.True);
}
}
}
}

View File

@@ -19,6 +19,8 @@ namespace Orchard.Tests.Stubs {
public class WorkContextImpl : WorkContext {
private readonly ILifetimeScope _lifetimeScope;
private Dictionary<string, object> _contextDictonary;
public delegate void MyInitMethod(WorkContextImpl workContextImpl);
public static MyInitMethod InitMethod;
public WorkContextImpl(ILifetimeScope lifetimeScope) {
_contextDictonary = new Dictionary<string, object>();
@@ -27,9 +29,15 @@ namespace Orchard.Tests.Stubs {
ci.Weld(new StubSite());
CurrentSite = ci.As<ISite>();
_lifetimeScope = lifetimeScope;
if (InitMethod != null) {
InitMethod(this);
}
}
public class StubSite : ContentPart, ISite {
public static string DefaultSuperUser;
public string PageTitleSeparator {
get { throw new NotImplementedException(); }
}
@@ -43,7 +51,7 @@ namespace Orchard.Tests.Stubs {
}
public string SuperUser {
get { throw new NotImplementedException(); }
get { return DefaultSuperUser; }
}
public string HomePage {

View File

@@ -74,11 +74,17 @@ namespace Orchard.Media.Controllers {
}
public ActionResult Edit(string name, string mediaPath) {
try {
IEnumerable<MediaFile> mediaFiles = _mediaService.GetMediaFiles(mediaPath);
IEnumerable<MediaFolder> mediaFolders = _mediaService.GetMediaFolders(mediaPath);
var model = new MediaFolderEditViewModel { FolderName = name, MediaFiles = mediaFiles, MediaFolders = mediaFolders, MediaPath = mediaPath };
return View(model);
}
catch (Exception exception) {
Services.Notifier.Error(T("Editing failed: {0}", exception.Message));
return RedirectToAction("Index");
}
}
[HttpPost]
public ActionResult Edit(FormCollection input) {
@@ -178,18 +184,8 @@ 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, viewModel.ExtractZip);
_mediaService.UploadMediaFile(viewModel.MediaPath, Request.Files[fileName], viewModel.ExtractZip);
}
Services.Notifier.Information(T("Media file(s) uploaded"));

View File

@@ -114,6 +114,7 @@
<SubType>Designer</SubType>
</Content>
</ItemGroup>
<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

@@ -1,19 +1,96 @@
using System.Collections.Generic;
using System.IO;
using System.Web;
using Orchard.Media.Models;
namespace Orchard.Media.Services {
public interface IMediaService : IDependency {
string GetPublicUrl(string path);
IEnumerable<MediaFolder> GetMediaFolders(string path);
IEnumerable<MediaFile> GetMediaFiles(string path);
void CreateFolder(string path, string name);
void DeleteFolder(string name);
void RenameFolder(string path, string newName);
void DeleteFile(string name, string folderName);
void RenameFile(string name, string newName, string folderName);
string UploadMediaFile(string folderName, string fileName, byte[] bytes, bool extractZip);
string UploadMediaFile(string folderName, HttpPostedFileBase postedFile, bool extractZip);
bool FileAllowed(HttpPostedFileBase postedFile);
/// <summary>
/// Retrieves the public path based on the relative path within the media directory.
/// </summary>
/// <example>
/// "/Media/Default/InnerDirectory/Test.txt" based on the input "InnerDirectory/Test.txt"
/// </example>
/// <param name="relativePath">The relative path within the media directory.</param>
/// <returns>The public path relative to the application url.</returns>
string GetPublicUrl(string relativePath);
/// <summary>
/// Retrieves the media folders within a given relative path.
/// </summary>
/// <param name="relativePath">The path where to retrieve the media folder from. null means root.</param>
/// <returns>The media folder in the given path.</returns>
IEnumerable<MediaFolder> GetMediaFolders(string relativePath);
/// <summary>
/// Retrieves the media files within a given relative path.
/// </summary>
/// <param name="relativePath">The path where to retrieve the media files from. null means root.</param>
/// <returns>The media files in the given path.</returns>
IEnumerable<MediaFile> GetMediaFiles(string relativePath);
/// <summary>
/// Creates a media folder.
/// </summary>
/// <param name="relativePath">The path where to create the new folder. null means root.</param>
/// <param name="folderName">The name of the folder to be created.</param>
void CreateFolder(string relativePath, string folderName);
/// <summary>
/// Deletes a media folder.
/// </summary>
/// <param name="folderPath">The path to the folder to be deleted.</param>
void DeleteFolder(string folderPath);
/// <summary>
/// Renames a media folder.
/// </summary>
/// <param name="folderPath">The path to the folder to be renamed.</param>
/// <param name="newFolderName">The new folder name.</param>
void RenameFolder(string folderPath, string newFolderName);
/// <summary>
/// Deletes a media file.
/// </summary>
/// <param name="folderPath">The folder path.</param>
/// <param name="fileName">The file name.</param>
void DeleteFile(string folderPath, string fileName);
/// <summary>
/// Renames a media file.
/// </summary>
/// <param name="folderPath">The path to the file's parent folder.</param>
/// <param name="currentFileName">The current file name.</param>
/// <param name="newFileName">The new file name.</param>
void RenameFile(string folderPath, string currentFileName, string newFileName);
/// <summary>
/// Uploads a media file based on a posted file.
/// </summary>
/// <param name="folderPath">The path to the folder where to upload the file.</param>
/// <param name="postedFile">The file to upload.</param>
/// <param name="extractZip">Boolean value indicating weather zip files should be extracted.</param>
/// <returns>The path to the uploaded file.</returns>
string UploadMediaFile(string folderPath, HttpPostedFileBase postedFile, bool extractZip);
/// <summary>
/// Uploads a media file based on an array of bytes.
/// </summary>
/// <param name="folderPath">The path to the folder where to upload the file.</param>
/// <param name="fileName">The file name.</param>
/// <param name="bytes">The array of bytes with the file's contents.</param>
/// <param name="extractZip">Boolean value indicating weather zip files should be extracted.</param>
/// <returns>The path to the uploaded file.</returns>
string UploadMediaFile(string folderPath, string fileName, byte[] bytes, bool extractZip);
/// <summary>
/// Uploads a media file based on a stream.
/// </summary>
/// <param name="folderPath">The folder path to where to upload the file.</param>
/// <param name="fileName">The file name.</param>
/// <param name="inputStream">The stream with the file's contents.</param>
/// <param name="extractZip">Boolean value indicating weather zip files should be extracted.</param>
/// <returns>The path to the uploaded file.</returns>
string UploadMediaFile(string folderPath, string fileName, Stream inputStream, bool extractZip);
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using ICSharpCode.SharpZipLib.Zip;
using JetBrains.Annotations;
@@ -8,13 +9,26 @@ using Orchard.ContentManagement;
using Orchard.FileSystems.Media;
using Orchard.Localization;
using Orchard.Media.Models;
using Orchard.Security;
using Orchard.Settings;
using Orchard.Validation;
namespace Orchard.Media.Services {
/// <summary>
/// The MediaService class provides the services o manipulate media entities (files / folders).
/// Among other things it provides filtering functionalities on file types.
/// The actual manipulation of the files is, however, delegated to the IStorageProvider.
/// </summary>
[UsedImplicitly]
public class MediaService : IMediaService {
private readonly IStorageProvider _storageProvider;
private readonly IOrchardServices _orchardServices;
/// <summary>
/// Initializes a new instance of the MediaService class with a given IStorageProvider and IOrchardServices.
/// </summary>
/// <param name="storageProvider">The storage provider.</param>
/// <param name="orchardServices">The orchard services provider.</param>
public MediaService(IStorageProvider storageProvider, IOrchardServices orchardServices) {
_storageProvider = storageProvider;
_orchardServices = orchardServices;
@@ -24,96 +38,167 @@ namespace Orchard.Media.Services {
public Localizer T { get; set; }
public string GetPublicUrl(string path) {
return _storageProvider.GetPublicUrl(path);
/// <summary>
/// Retrieves the public path based on the relative path within the media directory.
/// </summary>
/// <example>
/// "/Media/Default/InnerDirectory/Test.txt" based on the input "InnerDirectory/Test.txt"
/// </example>
/// <param name="relativePath">The relative path within the media directory.</param>
/// <returns>The public path relative to the application url.</returns>
public string GetPublicUrl(string relativePath) {
Argument.ThrowIfNullOrEmpty(relativePath, "relativePath");
return _storageProvider.GetPublicUrl(relativePath);
}
public IEnumerable<MediaFolder> GetMediaFolders(string path) {
var mediaFolders = new List<MediaFolder>();
var folders = _storageProvider.ListFolders(path);
foreach (var folder in folders) {
var mediaFolder = new MediaFolder {
/// <summary>
/// Retrieves the media folders within a given relative path.
/// </summary>
/// <param name="relativePath">The path where to retrieve the media folder from. null means root.</param>
/// <returns>The media folder in the given path.</returns>
public IEnumerable<MediaFolder> GetMediaFolders(string relativePath) {
return _storageProvider.ListFolders(relativePath).Select(folder =>
new MediaFolder {
Name = folder.GetName(),
Size = folder.GetSize(),
LastUpdated = folder.GetLastUpdated(),
MediaPath = folder.GetPath()
};
mediaFolders.Add(mediaFolder);
}
return mediaFolders;
});
}
public IEnumerable<MediaFile> GetMediaFiles(string path) {
var mediaFiles = new List<MediaFile>();
var files = _storageProvider.ListFiles(path);
foreach (var file in files) {
var mediaFile = new MediaFile {
/// <summary>
/// Retrieves the media files within a given relative path.
/// </summary>
/// <param name="relativePath">The path where to retrieve the media files from. null means root.</param>
/// <returns>The media files in the given path.</returns>
public IEnumerable<MediaFile> GetMediaFiles(string relativePath) {
return _storageProvider.ListFiles(relativePath).Select(file =>
new MediaFile {
Name = file.GetName(),
Size = file.GetSize(),
LastUpdated = file.GetLastUpdated(),
Type = file.GetFileType(),
FolderName = path
};
mediaFiles.Add(mediaFile);
}
return mediaFiles;
FolderName = relativePath
});
}
//TODO: Use Path.Combine.
public void CreateFolder(string mediaPath, string name) {
if (String.IsNullOrEmpty(mediaPath)) {
_storageProvider.CreateFolder(name);
return;
}
_storageProvider.CreateFolder(_storageProvider.Combine(mediaPath, name));
/// <summary>
/// Creates a media folder.
/// </summary>
/// <param name="relativePath">The path where to create the new folder. null means root.</param>
/// <param name="folderName">The name of the folder to be created.</param>
public void CreateFolder(string relativePath, string folderName) {
Argument.ThrowIfNullOrEmpty(folderName, "folderName");
_storageProvider.CreateFolder(relativePath == null ? folderName : _storageProvider.Combine(relativePath, folderName));
}
public void DeleteFolder(string name) {
_storageProvider.DeleteFolder(name);
/// <summary>
/// Deletes a media folder.
/// </summary>
/// <param name="folderPath">The path to the folder to be deleted.</param>
public void DeleteFolder(string folderPath) {
Argument.ThrowIfNullOrEmpty(folderPath, "folderPath");
_storageProvider.DeleteFolder(folderPath);
}
public void RenameFolder(string path, string newName) {
var newPath = RenameFolderPath(path, newName);
_storageProvider.RenameFolder(path, newPath);
/// <summary>
/// Renames a media folder.
/// </summary>
/// <param name="folderPath">The path to the folder to be renamed.</param>
/// <param name="newFolderName">The new folder name.</param>
public void RenameFolder(string folderPath, string newFolderName) {
Argument.ThrowIfNullOrEmpty(folderPath, "folderPath");
Argument.ThrowIfNullOrEmpty(newFolderName, "newFolderName");
_storageProvider.RenameFolder(folderPath, _storageProvider.Combine(Path.GetDirectoryName(folderPath), newFolderName));
}
public void DeleteFile(string name, string folderName) {
_storageProvider.DeleteFile(_storageProvider.Combine(folderName, name));
/// <summary>
/// Deletes a media file.
/// </summary>
/// <param name="folderPath">The folder path.</param>
/// <param name="fileName">The file name.</param>
public void DeleteFile(string folderPath, string fileName) {
Argument.ThrowIfNullOrEmpty(folderPath, "folderPath");
Argument.ThrowIfNullOrEmpty(fileName, "fileName");
_storageProvider.DeleteFile(_storageProvider.Combine(folderPath, fileName));
}
public void RenameFile(string name, string newName, string folderName) {
if (!FileAllowed(newName, false)) {
throw new ArgumentException(T("New file name {0} not allowed", newName).ToString());
/// <summary>
/// Renames a media file.
/// </summary>
/// <param name="folderPath">The path to the file's parent folder.</param>
/// <param name="currentFileName">The current file name.</param>
/// <param name="newFileName">The new file name.</param>
public void RenameFile(string folderPath, string currentFileName, string newFileName) {
Argument.ThrowIfNullOrEmpty(folderPath, "folderPath");
Argument.ThrowIfNullOrEmpty(currentFileName, "currentFileName");
Argument.ThrowIfNullOrEmpty(newFileName, "newFileName");
if (!FileAllowed(newFileName, false)) {
throw new ArgumentException(T("New file name {0} is not allowed", newFileName).ToString());
}
_storageProvider.RenameFile(_storageProvider.Combine(folderName, name), _storageProvider.Combine(folderName, newName));
_storageProvider.RenameFile(_storageProvider.Combine(folderPath, currentFileName), _storageProvider.Combine(Path.GetDirectoryName(folderPath), newFileName));
}
public string UploadMediaFile(string folderName, HttpPostedFileBase postedFile, bool extractZip) {
var postedFileLength = postedFile.ContentLength;
var postedFileStream = postedFile.InputStream;
var postedFileData = new byte[postedFileLength];
postedFileStream.Read(postedFileData, 0, postedFileLength);
/// <summary>
/// Uploads a media file based on a posted file.
/// </summary>
/// <param name="folderPath">The path to the folder where to upload the file.</param>
/// <param name="postedFile">The file to upload.</param>
/// <param name="extractZip">Boolean value indicating weather zip files should be extracted.</param>
/// <returns>The path to the uploaded file.</returns>
public string UploadMediaFile(string folderPath, HttpPostedFileBase postedFile, bool extractZip) {
Argument.ThrowIfNullOrEmpty(folderPath, "folderPath");
Argument.ThrowIfNull(postedFile, "postedFile");
return UploadMediaFile(folderName, postedFile.FileName, postedFileData, extractZip);
return UploadMediaFile(folderPath, postedFile.FileName, postedFile.InputStream, extractZip);
}
/// <summary>
/// Uploads a media file based on an array of bytes.
/// </summary>
/// <param name="folderPath">The path to the folder where to upload the file.</param>
/// <param name="fileName">The file name.</param>
/// <param name="bytes">The array of bytes with the file's contents.</param>
/// <param name="extractZip">Boolean value indicating weather zip files should be extracted.</param>
/// <returns>The path to the uploaded file.</returns>
public string UploadMediaFile(string folderPath, string fileName, byte [] bytes, bool extractZip) {
if (extractZip && fileName.EndsWith(".zip")) {
UnzipMediaFileArchive(folderPath, bytes);
Argument.ThrowIfNullOrEmpty(folderPath, "folderPath");
Argument.ThrowIfNullOrEmpty(fileName, "fileName");
Argument.ThrowIfNull(bytes, "bytes");
return UploadMediaFile(folderPath, fileName, new MemoryStream(bytes), extractZip);
}
/// <summary>
/// Uploads a media file based on a stream.
/// </summary>
/// <param name="folderPath">The folder path to where to upload the file.</param>
/// <param name="fileName">The file name.</param>
/// <param name="inputStream">The stream with the file's contents.</param>
/// <param name="extractZip">Boolean value indicating weather zip files should be extracted.</param>
/// <returns>The path to the uploaded file.</returns>
public string UploadMediaFile(string folderPath, string fileName, Stream inputStream, bool extractZip) {
Argument.ThrowIfNullOrEmpty(folderPath, "folderPath");
Argument.ThrowIfNullOrEmpty(fileName, "fileName");
Argument.ThrowIfNull(inputStream, "inputStream");
if (extractZip && IsZipFile(Path.GetExtension(fileName))) {
UnzipMediaFileArchive(folderPath, inputStream);
// Don't save the zip file.
return _storageProvider.GetPublicUrl(folderPath);
}
if (FileAllowed(fileName, true) && bytes.Length > 0) {
string filePath = Path.Combine(folderPath, Path.GetFileName(fileName));
_storageProvider.TryCreateFolder(folderPath);
IStorageFile file = _storageProvider.CreateFile(filePath);
using(var stream = file.OpenWrite()) {
stream.Write(bytes, 0, bytes.Length);
}
if (FileAllowed(fileName, true)) {
string filePath = _storageProvider.Combine(folderPath, fileName);
_storageProvider.SaveStream(filePath, inputStream);
return _storageProvider.GetPublicUrl(filePath);
}
@@ -121,69 +206,56 @@ namespace Orchard.Media.Services {
return null;
}
public bool FileAllowed(HttpPostedFileBase postedFile) {
if (postedFile == null) {
/// <summary>
/// Verifies if a file is allowed based on its name and the policies defined by the black / white lists.
/// </summary>
/// <param name="fileName">The file name of the file to validate.</param>
/// <param name="allowZip">Boolean value indicating weather zip files are allowed.</param>
/// <returns>True if the file is allowed; false if otherwise.</returns>
protected bool FileAllowed(string fileName, bool allowZip) {
string localFileName = GetFileName(fileName);
string extension = GetExtension(localFileName);
if (string.IsNullOrEmpty(localFileName) || string.IsNullOrEmpty(extension)) {
return false;
}
return FileAllowed(postedFile.FileName, true);
}
private bool FileAllowed(string name, bool allowZip) {
if (string.IsNullOrWhiteSpace(name)) {
return false;
}
var currentSite = _orchardServices.WorkContext.CurrentSite;
var mediaSettings = currentSite.As<MediaSettingsPart>();
var allowedExtensions = mediaSettings.UploadAllowedFileTypeWhitelist.ToUpperInvariant().Split(' ');
var ext = (Path.GetExtension(name) ?? "").TrimStart('.').ToUpperInvariant();
if (string.IsNullOrWhiteSpace(ext)) {
return false;
}
// whitelist does not apply to the superuser
var currentUser = _orchardServices.WorkContext.CurrentUser;
if (currentUser == null || !currentSite.SuperUser.Equals(currentUser.UserName, StringComparison.Ordinal)) {
ISite currentSite = _orchardServices.WorkContext.CurrentSite;
IUser currentUser = _orchardServices.WorkContext.CurrentUser;
// 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;
if (IsZipFile(extension)) {
return allowZip;
}
// whitelist does not apply to the superuser
if (currentUser == null || !currentSite.SuperUser.Equals(currentUser.UserName, StringComparison.Ordinal)) {
// must be in the whitelist
if (Array.IndexOf(allowedExtensions, ext) == -1) {
MediaSettingsPart mediaSettings = currentSite.As<MediaSettingsPart>();
if (mediaSettings == null ||
!mediaSettings.UploadAllowedFileTypeWhitelist.ToUpperInvariant().Split(' ').Contains(extension.ToUpperInvariant())) {
return false;
}
}
// blacklist always applies
if (string.Equals(name.Trim(), "web.config", StringComparison.OrdinalIgnoreCase)) {
if (string.Equals(localFileName, "web.config", StringComparison.OrdinalIgnoreCase)) {
return false;
}
return true;
}
private void SaveStream(string filePath, Stream inputStream) {
var file = _storageProvider.CreateFile(filePath);
var outputStream = file.OpenWrite();
var buffer = new byte[8192];
for (; ; ) {
/// <summary>
/// Unzips a media archive file.
/// </summary>
/// <param name="targetFolder">The folder where to unzip the file.</param>
/// <param name="zipStream">The archive file stream.</param>
protected void UnzipMediaFileArchive(string targetFolder, Stream zipStream) {
Argument.ThrowIfNullOrEmpty(targetFolder, "targetFolder");
Argument.ThrowIfNull(zipStream, "zipStream");
var length = inputStream.Read(buffer, 0, buffer.Length);
if (length <= 0)
break;
outputStream.Write(buffer, 0, length);
}
outputStream.Dispose();
}
private void UnzipMediaFileArchive(string targetFolder, HttpPostedFileBase postedFile) {
var postedFileLength = postedFile.ContentLength;
var postedFileStream = postedFile.InputStream;
var postedFileData = new byte[postedFileLength];
postedFileStream.Read(postedFileData, 0, postedFileLength);
UnzipMediaFileArchive(targetFolder, postedFileData);
}
private void UnzipMediaFileArchive(string targetFolder, byte [] postedFileData) {
using (var memoryStream = new MemoryStream(postedFileData)) {
var fileInflater = new ZipInputStream(memoryStream);
var fileInflater = new ZipInputStream(zipStream);
ZipEntry entry;
// We want to preserve whatever directory structure the zip file contained instead
// of flattening it.
@@ -192,29 +264,32 @@ namespace Orchard.Media.Services {
// before the directories that contain them, so we create directories as soon as first
// file below their path is encountered.
while ((entry = fileInflater.GetNextEntry()) != null) {
if (!entry.IsDirectory && entry.Name.Length > 0) {
var entryName = Path.Combine(targetFolder, entry.Name);
var directoryName = Path.GetDirectoryName(entryName);
if (!entry.IsDirectory && !string.IsNullOrEmpty(entry.Name)) {
// skip disallowed files
if (FileAllowed(entry.Name, false)) {
_storageProvider.TryCreateFolder(directoryName);
SaveStream(entryName, fileInflater);
}
string fullFileName = _storageProvider.Combine(targetFolder, entry.Name);
_storageProvider.TrySaveStream(fullFileName, fileInflater);
}
}
}
}
private string RenameFolderPath(string path, string newName) {
var lastIndex = Math.Max(path.LastIndexOf(Path.DirectorySeparatorChar), path.LastIndexOf(Path.AltDirectorySeparatorChar));
if (lastIndex == -1) {
return newName;
/// <summary>
/// Determines if a file is a Zip Archive based on its extension.
/// </summary>
/// <param name="extension">The extension of the file to analyze.</param>
/// <returns>True if the file is a Zip archive; false otherwise.</returns>
private static bool IsZipFile(string extension) {
return string.Equals(extension.TrimStart('.'), "zip", StringComparison.OrdinalIgnoreCase);
}
return _storageProvider.Combine(path.Substring(0, lastIndex), newName);
private static string GetFileName(string fileName) {
return Path.GetFileName(fileName).Trim();
}
private static string GetExtension(string fileName) {
return Path.GetExtension(fileName).Trim().TrimStart('.');
}
}
}

View File

@@ -6,6 +6,7 @@ using Orchard.Caching;
using Orchard.FileSystems.VirtualPath;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Validation;
namespace Orchard.FileSystems.AppData {
public class AppDataFolder : IAppDataFolder {
@@ -79,8 +80,7 @@ namespace Orchard.FileSystems.AppData {
/// starting with "_basePath".
/// </summary>
private string CombineToPhysicalPath(params string[] paths) {
return Path.Combine(RootFolder, Path.Combine(paths))
.Replace('/', Path.DirectorySeparatorChar);
return PathValidation.ValidatePath(RootFolder, Path.Combine(RootFolder, Path.Combine(paths)).Replace('/', Path.DirectorySeparatorChar));
}
/// <summary>

View File

@@ -1,4 +1,4 @@
#if !AZURE
#if !AZURE
using System;
using System.Collections.Generic;
using System.IO;
@@ -6,6 +6,7 @@ using System.Linq;
using System.Web.Hosting;
using Orchard.Environment.Configuration;
using Orchard.Localization;
using Orchard.Validation;
namespace Orchard.FileSystems.Media {
public class FileSystemStorageProvider : IStorageProvider {
@@ -35,11 +36,26 @@ namespace Orchard.FileSystems.Media {
public Localizer T { get; set; }
string Map(string path) {
return string.IsNullOrEmpty(path) ? _storagePath : Path.Combine(_storagePath, path);
/// <summary>
/// Maps a relative path into the storage path.
/// </summary>
/// <param name="path">The relative path to be mapped.</param>
/// <returns>The relative path combined with the storage path.</returns>
private string MapStorage(string path) {
string mappedPath = string.IsNullOrEmpty(path) ? _storagePath : Path.Combine(_storagePath, path);
return PathValidation.ValidatePath(_storagePath, mappedPath);
}
static string Fix(string path) {
/// <summary>
/// Maps a relative path into the public path.
/// </summary>
/// <param name="path">The relative path to be mapped.</param>
/// <returns>The relative path combined with the public path in an URL friendly format ('/' character for directory separator).</returns>
private string MapPublic(string path) {
return string.IsNullOrEmpty(path) ? _publicPath : Path.Combine(_publicPath, path).Replace(Path.DirectorySeparatorChar, '/');
}
private static string Fix(string path) {
return string.IsNullOrEmpty(path)
? ""
: Path.DirectorySeparatorChar != '/'
@@ -49,118 +65,230 @@ namespace Orchard.FileSystems.Media {
#region Implementation of IStorageProvider
/// <summary>
/// Retrieves the public URL for a given file within the storage provider.
/// </summary>
/// <param name="path">The relative path within the storage provider.</param>
/// <returns>The public URL.</returns>
public string GetPublicUrl(string path) {
return Map(_publicPath + path.Replace(Path.DirectorySeparatorChar, '/'));
return MapPublic(path);
}
/// <summary>
/// Retrieves a file within the storage provider.
/// </summary>
/// <param name="path">The relative path to the file within the storage provider.</param>
/// <returns>The file.</returns>
/// <exception cref="ArgumentException">If the file is not found.</exception>
public IStorageFile GetFile(string path) {
if (!File.Exists(Map(path))) {
FileInfo fileInfo = new FileInfo(MapStorage(path));
if (!fileInfo.Exists) {
throw new ArgumentException(T("File {0} does not exist", path).ToString());
}
return new FileSystemStorageFile(Fix(path), new FileInfo(Map(path)));
return new FileSystemStorageFile(Fix(path), fileInfo);
}
/// <summary>
/// Lists the files within a storage provider's path.
/// </summary>
/// <param name="path">The relative path to the folder which files to list.</param>
/// <returns>The list of files in the folder.</returns>
public IEnumerable<IStorageFile> ListFiles(string path) {
if (!Directory.Exists(Map(path))) {
DirectoryInfo directoryInfo = new DirectoryInfo(MapStorage(path));
if (!directoryInfo.Exists) {
throw new ArgumentException(T("Directory {0} does not exist", path).ToString());
}
return new DirectoryInfo(Map(path))
return directoryInfo
.GetFiles()
.Where(fi => !IsHidden(fi))
.Select<FileInfo, IStorageFile>(fi => new FileSystemStorageFile(Path.Combine(Fix(path), fi.Name), fi))
.ToList();
}
/// <summary>
/// Lists the folders within a storage provider's path.
/// </summary>
/// <param name="path">The relative path to the folder which folders to list.</param>
/// <returns>The list of folders in the folder.</returns>
public IEnumerable<IStorageFolder> ListFolders(string path) {
if (!Directory.Exists(Map(path))) {
DirectoryInfo directoryInfo = new DirectoryInfo(MapStorage(path));
if (!directoryInfo.Exists) {
try {
Directory.CreateDirectory(Map(path));
directoryInfo.Create();
}
catch (Exception ex) {
throw new ArgumentException(T("The folder could not be created at path: {0}. {1}", path, ex).ToString());
}
}
return new DirectoryInfo(Map(path))
return directoryInfo
.GetDirectories()
.Where(di => !IsHidden(di))
.Select<DirectoryInfo, IStorageFolder>(di => new FileSystemStorageFolder(Path.Combine(Fix(path), di.Name), di))
.ToList();
}
private static bool IsHidden(FileSystemInfo di) {
return (di.Attributes & FileAttributes.Hidden) != 0;
}
public void TryCreateFolder(string path) {
Directory.CreateDirectory(Map(path));
/// <summary>
/// Tries to create a folder in the storage provider.
/// </summary>
/// <param name="path">The relative path to the folder to be created.</param>
/// <returns>True if success; False otherwise.</returns>
public bool TryCreateFolder(string path) {
try { CreateFolder(path); }
catch { return false; }
return true;
}
/// <summary>
/// Creates a folder in the storage provider.
/// </summary>
/// <param name="path">The relative path to the folder to be created.</param>
/// <exception cref="ArgumentException">If the folder already exists.</exception>
public void CreateFolder(string path) {
if (Directory.Exists(Map(path))) {
DirectoryInfo directoryInfo = new DirectoryInfo(MapStorage(path));
if (directoryInfo.Exists) {
throw new ArgumentException(T("Directory {0} already exists", path).ToString());
}
TryCreateFolder(Map(path));
Directory.CreateDirectory(directoryInfo.FullName);
}
/// <summary>
/// Deletes a folder in the storage provider.
/// </summary>
/// <param name="path">The relative path to the folder to be deleted.</param>
/// <exception cref="ArgumentException">If the folder doesn't exist.</exception>
public void DeleteFolder(string path) {
if (!Directory.Exists(Map(path))) {
DirectoryInfo directoryInfo = new DirectoryInfo(MapStorage(path));
if (!directoryInfo.Exists) {
throw new ArgumentException(T("Directory {0} does not exist", path).ToString());
}
Directory.Delete(Map(path), true);
directoryInfo.Delete(true);
}
public void RenameFolder(string path, string newPath) {
if (!Directory.Exists(Map(path))) {
throw new ArgumentException(T("Directory {0} does not exist", path).ToString());
/// <summary>
/// Renames a folder in the storage provider.
/// </summary>
/// <param name="oldPath">The relative path to the folder to be renamed.</param>
/// <param name="newPath">The relative path to the new folder.</param>
public void RenameFolder(string oldPath, string newPath) {
DirectoryInfo sourceDirectory = new DirectoryInfo(MapStorage(oldPath));
if (!sourceDirectory.Exists) {
throw new ArgumentException(T("Directory {0} does not exist", oldPath).ToString());
}
if (Directory.Exists(Map(newPath))) {
DirectoryInfo targetDirectory = new DirectoryInfo(MapStorage(newPath));
if (targetDirectory.Exists) {
throw new ArgumentException(T("Directory {0} already exists", newPath).ToString());
}
Directory.Move(Map(path), Map(newPath));
Directory.Move(sourceDirectory.FullName, targetDirectory.FullName);
}
/// <summary>
/// Deletes a file in the storage provider.
/// </summary>
/// <param name="path">The relative path to the file to be deleted.</param>
/// <exception cref="ArgumentException">If the file doesn't exist.</exception>
public void DeleteFile(string path) {
FileInfo fileInfo = new FileInfo(MapStorage(path));
if (!fileInfo.Exists) {
throw new ArgumentException(T("File {0} does not exist", path).ToString());
}
fileInfo.Delete();
}
/// <summary>
/// Renames a file in the storage provider.
/// </summary>
/// <param name="oldPath">The relative path to the file to be renamed.</param>
/// <param name="newPath">The relative path to the new file.</param>
public void RenameFile(string oldPath, string newPath) {
FileInfo sourceFileInfo = new FileInfo(MapStorage(oldPath));
if (!sourceFileInfo.Exists) {
throw new ArgumentException(T("File {0} does not exist", oldPath).ToString());
}
FileInfo targetFileInfo = new FileInfo(MapStorage(newPath));
if (targetFileInfo.Exists) {
throw new ArgumentException(T("File {0} already exists", newPath).ToString());
}
File.Move(sourceFileInfo.FullName, targetFileInfo.FullName);
}
/// <summary>
/// Creates a file in the storage provider.
/// </summary>
/// <param name="path">The relative path to the file to be created.</param>
/// <exception cref="ArgumentException">If the file already exists.</exception>
/// <returns>The created file.</returns>
public IStorageFile CreateFile(string path) {
if (File.Exists(Map(path))) {
throw new ArgumentException(T("File {0} already exists", path).ToString());
FileInfo fileInfo = new FileInfo(MapStorage(path));
if (fileInfo.Exists) {
throw new ArgumentException(T("File {0} already exists", fileInfo.Name).ToString());
}
var fileInfo = new FileInfo(Map(path));
File.WriteAllBytes(Map(path), new byte[0]);
File.WriteAllBytes(fileInfo.FullName, new byte[0]);
return new FileSystemStorageFile(Fix(path), fileInfo);
}
public void DeleteFile(string path) {
if (!File.Exists(Map(path))) {
throw new ArgumentException(T("File {0} does not exist", path).ToString());
/// <summary>
/// Tries to save a stream in the storage provider.
/// </summary>
/// <param name="path">The relative path to the file to be created.</param>
/// <param name="inputStream">The stream to be saved.</param>
/// <returns>True if success; False otherwise.</returns>
public bool TrySaveStream(string path, Stream inputStream) {
try { SaveStream(path, inputStream); }
catch { return false; }
return true;
}
File.Delete(Map(path));
}
public void RenameFile(string path, string newPath) {
if (!File.Exists(Map(path))) {
throw new ArgumentException(T("File {0} does not exist", path).ToString());
}
if (File.Exists(Map(newPath))) {
throw new ArgumentException(T("File {0} already exists", newPath).ToString());
}
File.Move(Map(path), Map(newPath));
/// <summary>
/// Saves a stream in the storage provider.
/// </summary>
/// <param name="path">The relative path to the file to be created.</param>
/// <param name="inputStream">The stream to be saved.</param>
/// <exception cref="ArgumentException">If the stream can't be saved due to access permissions.</exception>
public void SaveStream(string path, Stream inputStream) {
// Create the file.
// The CreateFile method will map the still relative path
var file = CreateFile(path);
var outputStream = file.OpenWrite();
var buffer = new byte[8192];
for (;;) {
var length = inputStream.Read(buffer, 0, buffer.Length);
if (length <= 0)
break;
outputStream.Write(buffer, 0, length);
}
outputStream.Dispose();
}
/// <summary>
/// Combines to paths.
/// </summary>
/// <param name="path1">The parent path.</param>
/// <param name="path2">The child path.</param>
/// <returns>The combined path.</returns>
public string Combine(string path1, string path2) {
return Path.Combine(path1, path2);
}
private static bool IsHidden(FileSystemInfo di) {
return (di.Attributes & FileAttributes.Hidden) != 0;
}
#endregion
private class FileSystemStorageFile : IStorageFile {
@@ -264,7 +392,6 @@ namespace Orchard.FileSystems.Media {
return size;
}
}
}
}
#endif

View File

@@ -1,18 +1,110 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
namespace Orchard.FileSystems.Media {
public interface IStorageProvider : IDependency {
/// <summary>
/// Retrieves the public URL for a given file within the storage provider.
/// </summary>
/// <param name="path">The relative path within the storage provider.</param>
/// <returns>The public URL.</returns>
string GetPublicUrl(string path);
/// <summary>
/// Retrieves a file within the storage provider.
/// </summary>
/// <param name="path">The relative path to the file within the storage provider.</param>
/// <returns>The file.</returns>
/// <exception cref="ArgumentException">If the file is not found.</exception>
IStorageFile GetFile(string path);
/// <summary>
/// Lists the files within a storage provider's path.
/// </summary>
/// <param name="path">The relative path to the folder which files to list.</param>
/// <returns>The list of files in the folder.</returns>
IEnumerable<IStorageFile> ListFiles(string path);
/// <summary>
/// Lists the folders within a storage provider's path.
/// </summary>
/// <param name="path">The relative path to the folder which folders to list.</param>
/// <returns>The list of folders in the folder.</returns>
IEnumerable<IStorageFolder> ListFolders(string path);
void TryCreateFolder(string path);
/// <summary>
/// Tries to create a folder in the storage provider.
/// </summary>
/// <param name="path">The relative path to the folder to be created.</param>
/// <returns>True if success; False otherwise.</returns>
bool TryCreateFolder(string path);
/// <summary>
/// Creates a folder in the storage provider.
/// </summary>
/// <param name="path">The relative path to the folder to be created.</param>
/// <exception cref="ArgumentException">If the folder already exists.</exception>
void CreateFolder(string path);
/// <summary>
/// Deletes a folder in the storage provider.
/// </summary>
/// <param name="path">The relative path to the folder to be deleted.</param>
/// <exception cref="ArgumentException">If the folder doesn't exist.</exception>
void DeleteFolder(string path);
void RenameFolder(string path, string newPath);
/// <summary>
/// Renames a folder in the storage provider.
/// </summary>
/// <param name="oldPath">The relative path to the folder to be renamed.</param>
/// <param name="newPath">The relative path to the new folder.</param>
void RenameFolder(string oldPath, string newPath);
/// <summary>
/// Deletes a file in the storage provider.
/// </summary>
/// <param name="path">The relative path to the file to be deleted.</param>
/// <exception cref="ArgumentException">If the file doesn't exist.</exception>
void DeleteFile(string path);
void RenameFile(string path, string newPath);
/// <summary>
/// Renames a file in the storage provider.
/// </summary>
/// <param name="oldPath">The relative path to the file to be renamed.</param>
/// <param name="newPath">The relative path to the new file.</param>
void RenameFile(string oldPath, string newPath);
/// <summary>
/// Creates a file in the storage provider.
/// </summary>
/// <param name="path">The relative path to the file to be created.</param>
/// <exception cref="ArgumentException">If the file already exists.</exception>
/// <returns>The created file.</returns>
IStorageFile CreateFile(string path);
/// <summary>
/// Tries to save a stream in the storage provider.
/// </summary>
/// <param name="path">The relative path to the file to be created.</param>
/// <param name="inputStream">The stream to be saved.</param>
/// <returns>True if success; False otherwise.</returns>
bool TrySaveStream(string path, Stream inputStream);
/// <summary>
/// Saves a stream in the storage provider.
/// </summary>
/// <param name="path">The relative path to the file to be created.</param>
/// <param name="inputStream">The stream to be saved.</param>
/// <exception cref="ArgumentException">If the stream can't be saved due to access permissions.</exception>
void SaveStream(string path, Stream inputStream);
/// <summary>
/// Combines to paths.
/// </summary>
/// <param name="path1">The parent path.</param>
/// <param name="path2">The child path.</param>
/// <returns>The combined path.</returns>
string Combine(string path1, string path2);
}
}

View File

@@ -181,6 +181,7 @@
<Compile Include="Environment\WorkContextImplementation.cs" />
<Compile Include="Environment\WorkContextModule.cs" />
<Compile Include="Environment\WorkContextProperty.cs" />
<Compile Include="FileSystems\Media\FileSystemStorageProvider.cs" />
<Compile Include="Localization\Services\CurrentCultureWorkContext.cs" />
<Compile Include="Localization\Services\DefaultLocalizedStringManager.cs" />
<Compile Include="Localization\Services\ILocalizedStringManager.cs" />
@@ -459,6 +460,7 @@
<Compile Include="Messaging\Services\IMessageManager.cs" />
<Compile Include="Messaging\Services\IMessagingChannel.cs" />
<Compile Include="IWorkContextAccessor.cs" />
<Compile Include="Validation\PathValidation.cs" />
<Compile Include="WorkContextExtensions.cs" />
<Compile Include="Mvc\ViewEngines\Razor\RazorCompilationEventsShim.cs" />
<Compile Include="Mvc\ViewEngines\Razor\RazorViewEngineProvider.cs" />
@@ -799,7 +801,6 @@
<Compile Include="Security\Permissions\Permission.cs" />
<Compile Include="Security\Providers\OrchardRoleProvider.cs" />
<Compile Include="Services\Clock.cs" />
<Compile Include="FileSystems\Media\FileSystemStorageProvider.cs" />
<Compile Include="FileSystems\Media\IStorageFile.cs" />
<Compile Include="FileSystems\Media\IStorageFolder.cs" />
<Compile Include="FileSystems\Media\IStorageProvider.cs" />

View File

@@ -2,7 +2,7 @@
using JetBrains.Annotations;
namespace Orchard.Validation {
class Argument {
public class Argument {
[AssertionMethod]
public static void Validate([AssertionCondition(AssertionConditionType.IS_TRUE)] bool condition, [InvokerParameterName]string name) {
if (!condition) {

View File

@@ -0,0 +1,35 @@
using System;
using System.IO;
namespace Orchard.Validation {
/// <summary>
/// Provides methods to validate paths.
/// </summary>
public static class PathValidation {
/// <summary>
/// Determines if a path lies within the base path boundaries.
/// If not, an exception is thrown.
/// </summary>
/// <param name="basePath">The base path which boundaries are not to be transposed.</param>
/// <param name="mappedPath">The path to determine.</param>
/// <rereturns>The mapped path if valid.</rereturns>
/// <exception cref="ArgumentException">If the path is invalid.</exception>
public static string ValidatePath(string basePath, string mappedPath) {
bool valid = false;
try {
// Check that we are indeed within the storage directory boundaries
valid = Path.GetFullPath(mappedPath).StartsWith(Path.GetFullPath(basePath), StringComparison.OrdinalIgnoreCase);
} catch {
// Make sure that if invalid for medium trust we give a proper exception
valid = false;
}
if (!valid) {
throw new ArgumentException("Invalid path");
}
return mappedPath;
}
}
}