#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

@@ -17,8 +17,17 @@ Scenario: Creating a folder
And I fill in And I fill in
| name | value | | name | value |
| Name | Hello World | | Name | Hello World |
And I hit "Save" And I hit "Save"
And I am redirected And I am redirected
Then I should see "Manage Media Folders" Then I should see "Manage Media Folders"
And I should see "Hello World" And I should see "Hello World"
And the status should be 200 "OK" 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> // <auto-generated>
// This code was generated by SpecFlow (http://www.specflow.org/). // 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 // Runtime Version:4.0.30319.1
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
@@ -14,7 +14,7 @@ namespace Orchard.Specs
using TechTalk.SpecFlow; 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()] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.TestFixtureAttribute()]
[NUnit.Framework.DescriptionAttribute("Media management")] [NUnit.Framework.DescriptionAttribute("Media management")]
@@ -31,7 +31,7 @@ namespace Orchard.Specs
{ {
testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); 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" + 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); testRunner.OnFeatureStart(featureInfo);
} }
@@ -61,15 +61,15 @@ namespace Orchard.Specs
#line 6 #line 6
this.ScenarioSetup(scenarioInfo); this.ScenarioSetup(scenarioInfo);
#line 7 #line 7
testRunner.Given("I have installed Orchard"); testRunner.Given("I have installed Orchard");
#line 8 #line 8
testRunner.And("I have installed \"Orchard.Media\""); testRunner.And("I have installed \"Orchard.Media\"");
#line 9 #line 9
testRunner.When("I go to \"admin/media\""); testRunner.When("I go to \"admin/media\"");
#line 10 #line 10
testRunner.Then("I should see \"Manage Media Folders\""); testRunner.Then("I should see \"Manage Media Folders\"");
#line 11 #line 11
testRunner.And("the status should be 200 \"OK\""); testRunner.And("the status should be 200 \"OK\"");
#line hidden #line hidden
testRunner.CollectScenarioErrors(); testRunner.CollectScenarioErrors();
} }
@@ -82,11 +82,11 @@ testRunner.And("the status should be 200 \"OK\"");
#line 13 #line 13
this.ScenarioSetup(scenarioInfo); this.ScenarioSetup(scenarioInfo);
#line 14 #line 14
testRunner.Given("I have installed Orchard"); testRunner.Given("I have installed Orchard");
#line 15 #line 15
testRunner.And("I have installed \"Orchard.Media\""); testRunner.And("I have installed \"Orchard.Media\"");
#line 16 #line 16
testRunner.When("I go to \"admin/media/create\""); testRunner.When("I go to \"admin/media/create\"");
#line hidden #line hidden
TechTalk.SpecFlow.Table table1 = new TechTalk.SpecFlow.Table(new string[] { TechTalk.SpecFlow.Table table1 = new TechTalk.SpecFlow.Table(new string[] {
"name", "name",
@@ -95,17 +95,42 @@ testRunner.When("I go to \"admin/media/create\"");
"Name", "Name",
"Hello World"}); "Hello World"});
#line 17 #line 17
testRunner.And("I fill in", ((string)(null)), table1); testRunner.And("I fill in", ((string)(null)), table1);
#line 20 #line 20
testRunner.And("I hit \"Save\""); testRunner.And("I hit \"Save\"");
#line 21 #line 21
testRunner.And("I am redirected"); testRunner.And("I am redirected");
#line 22 #line 22
testRunner.Then("I should see \"Manage Media Folders\""); testRunner.Then("I should see \"Manage Media Folders\"");
#line 23 #line 23
testRunner.And("I should see \"Hello World\""); testRunner.And("I should see \"Hello World\"");
#line 24 #line 24
testRunner.And("the status should be 200 \"OK\""); 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 #line hidden
testRunner.CollectScenarioErrors(); 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"> <Reference Include="FluentPath">
<HintPath>..\..\lib\fluentpath\FluentPath.dll</HintPath> <HintPath>..\..\lib\fluentpath\FluentPath.dll</HintPath>
</Reference> </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"> <Reference Include="IronRuby, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\dlr\IronRuby.dll</HintPath> <HintPath>..\..\lib\dlr\IronRuby.dll</HintPath>
@@ -138,6 +142,7 @@
<Compile Include="Comments\Services\CommentServiceTests.cs" /> <Compile Include="Comments\Services\CommentServiceTests.cs" />
<Compile Include="Indexing\LuceneIndexProviderTests.cs" /> <Compile Include="Indexing\LuceneIndexProviderTests.cs" />
<Compile Include="Indexing\LuceneSearchBuilderTests.cs" /> <Compile Include="Indexing\LuceneSearchBuilderTests.cs" />
<Compile Include="Media\Services\MediaServiceTests.cs" />
<Compile Include="Scripting.Dlr\EvaluatorTests.cs" /> <Compile Include="Scripting.Dlr\EvaluatorTests.cs" />
<Compile Include="Scripting\EvaluatorTestsBase.cs" /> <Compile Include="Scripting\EvaluatorTestsBase.cs" />
<Compile Include="Scripting\EvaluatorTests.cs" /> <Compile Include="Scripting\EvaluatorTests.cs" />

View File

@@ -253,6 +253,7 @@
<Compile Include="Mvc\Routes\UrlPrefixTests.cs" /> <Compile Include="Mvc\Routes\UrlPrefixTests.cs" />
<Compile Include="Records\BigRecord.cs" /> <Compile Include="Records\BigRecord.cs" />
<Compile Include="Security\DefaultEncryptionServiceTests.cs" /> <Compile Include="Security\DefaultEncryptionServiceTests.cs" />
<Compile Include="Storage\FileSystemStorageProviderTests.cs" />
<Compile Include="Stubs\InMemoryWebSiteFolder.cs" /> <Compile Include="Stubs\InMemoryWebSiteFolder.cs" />
<Compile Include="Stubs\StubHttpContextAccessor.cs" /> <Compile Include="Stubs\StubHttpContextAccessor.cs" />
<Compile Include="Stubs\StubWorkContextAccessor.cs" /> <Compile Include="Stubs\StubWorkContextAccessor.cs" />
@@ -285,7 +286,6 @@
<Compile Include="UI\Notify\NotifierTests.cs" /> <Compile Include="UI\Notify\NotifierTests.cs" />
<Compile Include="UI\Notify\NotifyFilterTests.cs" /> <Compile Include="UI\Notify\NotifyFilterTests.cs" />
<Compile Include="Services\ClockTests.cs" /> <Compile Include="Services\ClockTests.cs" />
<Compile Include="Storage\FileSystemStorageProviderTests.cs" />
<Compile Include="Stubs\StubClock.cs" /> <Compile Include="Stubs\StubClock.cs" />
<Compile Include="Stubs\StubContainerProvider.cs" /> <Compile Include="Stubs\StubContainerProvider.cs" />
<Compile Include="FakeTests.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"); _folderPath = Path.Combine(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Media"), "Default");
_filePath = _folderPath + "\\testfile.txt"; _filePath = _folderPath + "\\testfile.txt";
if (Directory.Exists(_folderPath)) {
Directory.Delete(_folderPath, true);
}
Directory.CreateDirectory(_folderPath); Directory.CreateDirectory(_folderPath);
File.WriteAllText(_filePath, "testfile contents"); 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", "testfile4.txt")), Is.Null);
Assert.That(GetFile(Path.Combine("SubFolder1", "testfile5.txt")), Is.Not.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 { public class WorkContextImpl : WorkContext {
private readonly ILifetimeScope _lifetimeScope; private readonly ILifetimeScope _lifetimeScope;
private Dictionary<string, object> _contextDictonary; private Dictionary<string, object> _contextDictonary;
public delegate void MyInitMethod(WorkContextImpl workContextImpl);
public static MyInitMethod InitMethod;
public WorkContextImpl(ILifetimeScope lifetimeScope) { public WorkContextImpl(ILifetimeScope lifetimeScope) {
_contextDictonary = new Dictionary<string, object>(); _contextDictonary = new Dictionary<string, object>();
@@ -27,9 +29,15 @@ namespace Orchard.Tests.Stubs {
ci.Weld(new StubSite()); ci.Weld(new StubSite());
CurrentSite = ci.As<ISite>(); CurrentSite = ci.As<ISite>();
_lifetimeScope = lifetimeScope; _lifetimeScope = lifetimeScope;
if (InitMethod != null) {
InitMethod(this);
}
} }
public class StubSite : ContentPart, ISite { public class StubSite : ContentPart, ISite {
public static string DefaultSuperUser;
public string PageTitleSeparator { public string PageTitleSeparator {
get { throw new NotImplementedException(); } get { throw new NotImplementedException(); }
} }
@@ -43,7 +51,7 @@ namespace Orchard.Tests.Stubs {
} }
public string SuperUser { public string SuperUser {
get { throw new NotImplementedException(); } get { return DefaultSuperUser; }
} }
public string HomePage { public string HomePage {

View File

@@ -74,10 +74,16 @@ namespace Orchard.Media.Controllers {
} }
public ActionResult Edit(string name, string mediaPath) { public ActionResult Edit(string name, string mediaPath) {
IEnumerable<MediaFile> mediaFiles = _mediaService.GetMediaFiles(mediaPath); try {
IEnumerable<MediaFolder> mediaFolders = _mediaService.GetMediaFolders(mediaPath); IEnumerable<MediaFile> mediaFiles = _mediaService.GetMediaFiles(mediaPath);
var model = new MediaFolderEditViewModel { FolderName = name, MediaFiles = mediaFiles, MediaFolders = mediaFolders, MediaPath = mediaPath }; IEnumerable<MediaFolder> mediaFolders = _mediaService.GetMediaFolders(mediaPath);
return View(model); 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] [HttpPost]
@@ -178,18 +184,8 @@ namespace Orchard.Media.Controllers {
if (!ModelState.IsValid) if (!ModelState.IsValid)
return View(viewModel); return View(viewModel);
// first validate them all
foreach (string fileName in Request.Files) { foreach (string fileName in Request.Files) {
HttpPostedFileBase file = Request.Files[fileName]; _mediaService.UploadMediaFile(viewModel.MediaPath, Request.Files[fileName], viewModel.ExtractZip);
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);
} }
Services.Notifier.Information(T("Media file(s) uploaded")); Services.Notifier.Information(T("Media file(s) uploaded"));

View File

@@ -114,6 +114,7 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.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. <!-- 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.Collections.Generic;
using System.IO;
using System.Web; using System.Web;
using Orchard.Media.Models; using Orchard.Media.Models;
namespace Orchard.Media.Services { namespace Orchard.Media.Services {
public interface IMediaService : IDependency { public interface IMediaService : IDependency {
string GetPublicUrl(string path); /// <summary>
IEnumerable<MediaFolder> GetMediaFolders(string path); /// Retrieves the public path based on the relative path within the media directory.
IEnumerable<MediaFile> GetMediaFiles(string path); /// </summary>
void CreateFolder(string path, string name); /// <example>
void DeleteFolder(string name); /// "/Media/Default/InnerDirectory/Test.txt" based on the input "InnerDirectory/Test.txt"
void RenameFolder(string path, string newName); /// </example>
void DeleteFile(string name, string folderName); /// <param name="relativePath">The relative path within the media directory.</param>
void RenameFile(string name, string newName, string folderName); /// <returns>The public path relative to the application url.</returns>
string UploadMediaFile(string folderName, string fileName, byte[] bytes, bool extractZip); string GetPublicUrl(string relativePath);
string UploadMediaFile(string folderName, HttpPostedFileBase postedFile, bool extractZip);
bool FileAllowed(HttpPostedFileBase postedFile); /// <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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Web; using System.Web;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -8,13 +9,26 @@ using Orchard.ContentManagement;
using Orchard.FileSystems.Media; using Orchard.FileSystems.Media;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Media.Models; using Orchard.Media.Models;
using Orchard.Security;
using Orchard.Settings;
using Orchard.Validation;
namespace Orchard.Media.Services { 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] [UsedImplicitly]
public class MediaService : IMediaService { public class MediaService : IMediaService {
private readonly IStorageProvider _storageProvider; private readonly IStorageProvider _storageProvider;
private readonly IOrchardServices _orchardServices; 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) { public MediaService(IStorageProvider storageProvider, IOrchardServices orchardServices) {
_storageProvider = storageProvider; _storageProvider = storageProvider;
_orchardServices = orchardServices; _orchardServices = orchardServices;
@@ -24,96 +38,167 @@ namespace Orchard.Media.Services {
public Localizer T { get; set; } public Localizer T { get; set; }
public string GetPublicUrl(string path) { /// <summary>
return _storageProvider.GetPublicUrl(path); /// 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");
public IEnumerable<MediaFolder> GetMediaFolders(string path) { return _storageProvider.GetPublicUrl(relativePath);
var mediaFolders = new List<MediaFolder>(); }
var folders = _storageProvider.ListFolders(path);
foreach (var folder in folders) { /// <summary>
var mediaFolder = new MediaFolder { /// 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(), Name = folder.GetName(),
Size = folder.GetSize(), Size = folder.GetSize(),
LastUpdated = folder.GetLastUpdated(), LastUpdated = folder.GetLastUpdated(),
MediaPath = folder.GetPath() MediaPath = folder.GetPath()
}; });
mediaFolders.Add(mediaFolder);
}
return mediaFolders;
} }
public IEnumerable<MediaFile> GetMediaFiles(string path) { /// <summary>
var mediaFiles = new List<MediaFile>(); /// Retrieves the media files within a given relative path.
/// </summary>
var files = _storageProvider.ListFiles(path); /// <param name="relativePath">The path where to retrieve the media files from. null means root.</param>
foreach (var file in files) { /// <returns>The media files in the given path.</returns>
var mediaFile = new MediaFile { public IEnumerable<MediaFile> GetMediaFiles(string relativePath) {
return _storageProvider.ListFiles(relativePath).Select(file =>
new MediaFile {
Name = file.GetName(), Name = file.GetName(),
Size = file.GetSize(), Size = file.GetSize(),
LastUpdated = file.GetLastUpdated(), LastUpdated = file.GetLastUpdated(),
Type = file.GetFileType(), Type = file.GetFileType(),
FolderName = path FolderName = relativePath
}; });
mediaFiles.Add(mediaFile);
}
return mediaFiles;
} }
//TODO: Use Path.Combine. /// <summary>
public void CreateFolder(string mediaPath, string name) { /// Creates a media folder.
if (String.IsNullOrEmpty(mediaPath)) { /// </summary>
_storageProvider.CreateFolder(name); /// <param name="relativePath">The path where to create the new folder. null means root.</param>
return; /// <param name="folderName">The name of the folder to be created.</param>
} public void CreateFolder(string relativePath, string folderName) {
_storageProvider.CreateFolder(_storageProvider.Combine(mediaPath, name)); Argument.ThrowIfNullOrEmpty(folderName, "folderName");
_storageProvider.CreateFolder(relativePath == null ? folderName : _storageProvider.Combine(relativePath, folderName));
} }
public void DeleteFolder(string name) { /// <summary>
_storageProvider.DeleteFolder(name); /// 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) { /// <summary>
var newPath = RenameFolderPath(path, newName); /// Renames a media folder.
_storageProvider.RenameFolder(path, newPath); /// </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) { /// <summary>
_storageProvider.DeleteFile(_storageProvider.Combine(folderName, name)); /// 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) { /// <summary>
if (!FileAllowed(newName, false)) { /// Renames a media file.
throw new ArgumentException(T("New file name {0} not allowed", newName).ToString()); /// </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) { /// <summary>
var postedFileLength = postedFile.ContentLength; /// Uploads a media file based on a posted file.
var postedFileStream = postedFile.InputStream; /// </summary>
var postedFileData = new byte[postedFileLength]; /// <param name="folderPath">The path to the folder where to upload the file.</param>
postedFileStream.Read(postedFileData, 0, postedFileLength); /// <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) { public string UploadMediaFile(string folderPath, string fileName, byte [] bytes, bool extractZip) {
if (extractZip && fileName.EndsWith(".zip")) { Argument.ThrowIfNullOrEmpty(folderPath, "folderPath");
UnzipMediaFileArchive(folderPath, bytes); 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. // Don't save the zip file.
return _storageProvider.GetPublicUrl(folderPath); return _storageProvider.GetPublicUrl(folderPath);
} }
if (FileAllowed(fileName, true) && bytes.Length > 0) { if (FileAllowed(fileName, true)) {
string filePath = Path.Combine(folderPath, Path.GetFileName(fileName)); string filePath = _storageProvider.Combine(folderPath, fileName);
_storageProvider.TryCreateFolder(folderPath); _storageProvider.SaveStream(filePath, inputStream);
IStorageFile file = _storageProvider.CreateFile(filePath);
using(var stream = file.OpenWrite()) {
stream.Write(bytes, 0, bytes.Length);
}
return _storageProvider.GetPublicUrl(filePath); return _storageProvider.GetPublicUrl(filePath);
} }
@@ -121,100 +206,90 @@ namespace Orchard.Media.Services {
return null; return null;
} }
public bool FileAllowed(HttpPostedFileBase postedFile) { /// <summary>
if (postedFile == null) { /// 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 false;
} }
return FileAllowed(postedFile.FileName, true);
}
private bool FileAllowed(string name, bool allowZip) { ISite currentSite = _orchardServices.WorkContext.CurrentSite;
if (string.IsNullOrWhiteSpace(name)) { IUser currentUser = _orchardServices.WorkContext.CurrentUser;
return false;
} // zip files at the top level are allowed since this is how you upload multiple files at once.
var currentSite = _orchardServices.WorkContext.CurrentSite; if (IsZipFile(extension)) {
var mediaSettings = currentSite.As<MediaSettingsPart>(); return allowZip;
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 // whitelist does not apply to the superuser
var currentUser = _orchardServices.WorkContext.CurrentUser;
if (currentUser == null || !currentSite.SuperUser.Equals(currentUser.UserName, StringComparison.Ordinal)) { 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 // 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; return false;
} }
} }
// blacklist always applies // blacklist always applies
if (string.Equals(name.Trim(), "web.config", StringComparison.OrdinalIgnoreCase)) { if (string.Equals(localFileName, "web.config", StringComparison.OrdinalIgnoreCase)) {
return false; return false;
} }
return true; return true;
} }
private void SaveStream(string filePath, Stream inputStream) { /// <summary>
var file = _storageProvider.CreateFile(filePath); /// Unzips a media archive file.
var outputStream = file.OpenWrite(); /// </summary>
var buffer = new byte[8192]; /// <param name="targetFolder">The folder where to unzip the file.</param>
for (; ; ) { /// <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); var fileInflater = new ZipInputStream(zipStream);
if (length <= 0) ZipEntry entry;
break; // We want to preserve whatever directory structure the zip file contained instead
outputStream.Write(buffer, 0, length); // of flattening it.
} // The API below doesn't necessarily return the entries in the zip file in any order.
outputStream.Dispose(); // That means the files in subdirectories can be returned as entries from the stream
} // 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 && !string.IsNullOrEmpty(entry.Name)) {
private void UnzipMediaFileArchive(string targetFolder, HttpPostedFileBase postedFile) { // skip disallowed files
var postedFileLength = postedFile.ContentLength; if (FileAllowed(entry.Name, false)) {
var postedFileStream = postedFile.InputStream; string fullFileName = _storageProvider.Combine(targetFolder, entry.Name);
var postedFileData = new byte[postedFileLength]; _storageProvider.TrySaveStream(fullFileName, fileInflater);
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);
ZipEntry entry;
// We want to preserve whatever directory structure the zip file contained instead
// of flattening it.
// The API below doesn't necessarily return the entries in the zip file in any order.
// That means the files in subdirectories can be returned as entries from the stream
// 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);
// skip disallowed files
if (FileAllowed(entry.Name, false)) {
_storageProvider.TryCreateFolder(directoryName);
SaveStream(entryName, fileInflater);
}
} }
} }
} }
} }
private string RenameFolderPath(string path, string newName) { /// <summary>
var lastIndex = Math.Max(path.LastIndexOf(Path.DirectorySeparatorChar), path.LastIndexOf(Path.AltDirectorySeparatorChar)); /// 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);
}
if (lastIndex == -1) { private static string GetFileName(string fileName) {
return newName; return Path.GetFileName(fileName).Trim();
} }
return _storageProvider.Combine(path.Substring(0, lastIndex), newName); 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.FileSystems.VirtualPath;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Logging; using Orchard.Logging;
using Orchard.Validation;
namespace Orchard.FileSystems.AppData { namespace Orchard.FileSystems.AppData {
public class AppDataFolder : IAppDataFolder { public class AppDataFolder : IAppDataFolder {
@@ -79,8 +80,7 @@ namespace Orchard.FileSystems.AppData {
/// starting with "_basePath". /// starting with "_basePath".
/// </summary> /// </summary>
private string CombineToPhysicalPath(params string[] paths) { private string CombineToPhysicalPath(params string[] paths) {
return Path.Combine(RootFolder, Path.Combine(paths)) return PathValidation.ValidatePath(RootFolder, Path.Combine(RootFolder, Path.Combine(paths)).Replace('/', Path.DirectorySeparatorChar));
.Replace('/', Path.DirectorySeparatorChar);
} }
/// <summary> /// <summary>

View File

@@ -1,4 +1,4 @@
#if !AZURE #if !AZURE
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -6,6 +6,7 @@ using System.Linq;
using System.Web.Hosting; using System.Web.Hosting;
using Orchard.Environment.Configuration; using Orchard.Environment.Configuration;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Validation;
namespace Orchard.FileSystems.Media { namespace Orchard.FileSystems.Media {
public class FileSystemStorageProvider : IStorageProvider { public class FileSystemStorageProvider : IStorageProvider {
@@ -16,7 +17,7 @@ namespace Orchard.FileSystems.Media {
var mediaPath = HostingEnvironment.IsHosted var mediaPath = HostingEnvironment.IsHosted
? HostingEnvironment.MapPath("~/Media/") ?? "" ? HostingEnvironment.MapPath("~/Media/") ?? ""
: Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Media"); : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Media");
_storagePath = Path.Combine(mediaPath, settings.Name); _storagePath = Path.Combine(mediaPath, settings.Name);
var appPath = ""; var appPath = "";
@@ -35,11 +36,26 @@ namespace Orchard.FileSystems.Media {
public Localizer T { get; set; } public Localizer T { get; set; }
string Map(string path) { /// <summary>
return string.IsNullOrEmpty(path) ? _storagePath : Path.Combine(_storagePath, path); /// 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) return string.IsNullOrEmpty(path)
? "" ? ""
: Path.DirectorySeparatorChar != '/' : Path.DirectorySeparatorChar != '/'
@@ -49,118 +65,230 @@ namespace Orchard.FileSystems.Media {
#region Implementation of IStorageProvider #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) { public string GetPublicUrl(string path) {
return MapPublic(path);
return Map(_publicPath + path.Replace(Path.DirectorySeparatorChar, '/'));
} }
/// <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) { 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()); 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) { 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()); throw new ArgumentException(T("Directory {0} does not exist", path).ToString());
} }
return new DirectoryInfo(Map(path)) return directoryInfo
.GetFiles() .GetFiles()
.Where(fi => !IsHidden(fi)) .Where(fi => !IsHidden(fi))
.Select<FileInfo, IStorageFile>(fi => new FileSystemStorageFile(Path.Combine(Fix(path), fi.Name), fi)) .Select<FileInfo, IStorageFile>(fi => new FileSystemStorageFile(Path.Combine(Fix(path), fi.Name), fi))
.ToList(); .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) { public IEnumerable<IStorageFolder> ListFolders(string path) {
if (!Directory.Exists(Map(path))) { DirectoryInfo directoryInfo = new DirectoryInfo(MapStorage(path));
if (!directoryInfo.Exists) {
try { try {
Directory.CreateDirectory(Map(path)); directoryInfo.Create();
} }
catch (Exception ex) { catch (Exception ex) {
throw new ArgumentException(T("The folder could not be created at path: {0}. {1}", path, ex).ToString()); 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() .GetDirectories()
.Where(di => !IsHidden(di)) .Where(di => !IsHidden(di))
.Select<DirectoryInfo, IStorageFolder>(di => new FileSystemStorageFolder(Path.Combine(Fix(path), di.Name), di)) .Select<DirectoryInfo, IStorageFolder>(di => new FileSystemStorageFolder(Path.Combine(Fix(path), di.Name), di))
.ToList(); .ToList();
} }
private static bool IsHidden(FileSystemInfo di) { /// <summary>
return (di.Attributes & FileAttributes.Hidden) != 0; /// Tries to create a folder in the storage provider.
} /// </summary>
/// <param name="path">The relative path to the folder to be created.</param>
public void TryCreateFolder(string path) { /// <returns>True if success; False otherwise.</returns>
Directory.CreateDirectory(Map(path)); 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) { 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()); 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) { 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()); 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) { /// <summary>
if (!Directory.Exists(Map(path))) { /// Renames a folder in the storage provider.
throw new ArgumentException(T("Directory {0} does not exist", path).ToString()); /// </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()); throw new ArgumentException(T("Directory {0} already exists", newPath).ToString());
} }
Directory.Move(Map(path), Map(newPath)); Directory.Move(sourceDirectory.FullName, targetDirectory.FullName);
} }
public IStorageFile CreateFile(string path) { /// <summary>
if (File.Exists(Map(path))) { /// Deletes a file in the storage provider.
throw new ArgumentException(T("File {0} already exists", path).ToString()); /// </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());
} }
var fileInfo = new FileInfo(Map(path)); fileInfo.Delete();
File.WriteAllBytes(Map(path), new byte[0]); }
/// <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) {
FileInfo fileInfo = new FileInfo(MapStorage(path));
if (fileInfo.Exists) {
throw new ArgumentException(T("File {0} already exists", fileInfo.Name).ToString());
}
File.WriteAllBytes(fileInfo.FullName, new byte[0]);
return new FileSystemStorageFile(Fix(path), fileInfo); return new FileSystemStorageFile(Fix(path), fileInfo);
} }
public void DeleteFile(string path) { /// <summary>
if (!File.Exists(Map(path))) { /// Tries to save a stream in the storage provider.
throw new ArgumentException(T("File {0} does not exist", path).ToString()); /// </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; }
File.Delete(Map(path)); return true;
} }
public void RenameFile(string path, string newPath) { /// <summary>
if (!File.Exists(Map(path))) { /// Saves a stream in the storage provider.
throw new ArgumentException(T("File {0} does not exist", path).ToString()); /// </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);
if (File.Exists(Map(newPath))) { var outputStream = file.OpenWrite();
throw new ArgumentException(T("File {0} already exists", newPath).ToString()); var buffer = new byte[8192];
} for (;;) {
File.Move(Map(path), Map(newPath)); 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) { public string Combine(string path1, string path2) {
return Path.Combine(path1, path2); return Path.Combine(path1, path2);
} }
private static bool IsHidden(FileSystemInfo di) {
return (di.Attributes & FileAttributes.Hidden) != 0;
}
#endregion #endregion
private class FileSystemStorageFile : IStorageFile { private class FileSystemStorageFile : IStorageFile {
@@ -264,7 +392,6 @@ namespace Orchard.FileSystems.Media {
return size; return size;
} }
} }
} }
} }
#endif #endif

View File

@@ -1,18 +1,110 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.IO;
namespace Orchard.FileSystems.Media { namespace Orchard.FileSystems.Media {
public interface IStorageProvider : IDependency { 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); 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); 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); 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); 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); 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 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 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); 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); string Combine(string path1, string path2);
} }
} }

View File

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

View File

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