#17238: A module's default route URL should be a valid URL without the need for further escaping.

- adding a Path property on ExtensionDescriptor that's used in place of the area name for default route URLs
- the Path property if not set will be the Name if it's a valid URL segment, otherwise it will be the Id
- if an *invalid* Path is given an error will be logged and the extension will not be loaded

--HG--
branch : dev
This commit is contained in:
Nathan Heskew
2011-02-01 15:56:12 -08:00
parent 4c25a7cc29
commit 1b82ea74f9
11 changed files with 108 additions and 20 deletions

View File

@@ -48,12 +48,15 @@ namespace Orchard.Tests.Environment.Extensions {
} }
[Test] [Test]
public void NamesFromFoldersWithModuleTxtShouldBeListed() { public void IdsFromFoldersWithModuleTxtShouldBeListed() {
IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, new StubCacheManager(), new StubWebSiteFolder()); IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, new StubCacheManager(), new StubWebSiteFolder());
var names = folders.AvailableExtensions().Select(d => d.Id); var ids = folders.AvailableExtensions().Select(d => d.Id);
Assert.That(names.Count(), Is.EqualTo(2)); Assert.That(ids.Count(), Is.EqualTo(5));
Assert.That(names, Has.Some.EqualTo("Sample1")); Assert.That(ids, Has.Some.EqualTo("Sample1")); // Sample1 - obviously
Assert.That(names, Has.Some.EqualTo("Sample3")); Assert.That(ids, Has.Some.EqualTo("Sample3")); // Sample3
Assert.That(ids, Has.Some.EqualTo("Sample4")); // Sample4
Assert.That(ids, Has.Some.EqualTo("Sample6")); // Sample6
Assert.That(ids, Has.Some.EqualTo("Sample7")); // Sample7
} }
[Test] [Test]
@@ -61,7 +64,31 @@ namespace Orchard.Tests.Environment.Extensions {
IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, new StubCacheManager(), new StubWebSiteFolder()); IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, new StubCacheManager(), new StubWebSiteFolder());
var sample1 = folders.AvailableExtensions().Single(d => d.Id == "Sample1"); var sample1 = folders.AvailableExtensions().Single(d => d.Id == "Sample1");
Assert.That(sample1.Id, Is.Not.Empty); Assert.That(sample1.Id, Is.Not.Empty);
Assert.That(sample1.Author, Is.EqualTo("Bertrand Le Roy")); Assert.That(sample1.Author, Is.EqualTo("Bertrand Le Roy")); // Sample1
} }
}
[Test]
public void NamesFromFoldersWithModuleTxtShouldFallBackToIdIfNotGiven() {
IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, new StubCacheManager(), new StubWebSiteFolder());
var names = folders.AvailableExtensions().Select(d => d.Name);
Assert.That(names.Count(), Is.EqualTo(5));
Assert.That(names, Has.Some.EqualTo("Le plug-in français")); // Sample1
Assert.That(names, Has.Some.EqualTo("This is another test.txt")); // Sample3
Assert.That(names, Has.Some.EqualTo("Sample4")); // Sample4
Assert.That(names, Has.Some.EqualTo("SampleSix")); // Sample6
Assert.That(names, Has.Some.EqualTo("Sample7")); // Sample7
}
[Test]
public void PathsFromFoldersWithModuleTxtShouldFallBackAppropriatelyIfNotGiven() {
IExtensionFolders folders = new ModuleFolders(new[] { _tempFolderName }, new StubCacheManager(), new StubWebSiteFolder());
var paths = folders.AvailableExtensions().Select(d => d.Path);
Assert.That(paths.Count(), Is.EqualTo(5));
Assert.That(paths, Has.Some.EqualTo("Sample1")); // Sample1 - Id, Name invalid URL segment
Assert.That(paths, Has.Some.EqualTo("Sample3")); // Sample3 - Id, Name invalid URL segment
Assert.That(paths, Has.Some.EqualTo("ThisIs.Sample4")); // Sample4 - Path
Assert.That(paths, Has.Some.EqualTo("SampleSix")); // Sample6 - Name, no Path
Assert.That(paths, Has.Some.EqualTo("Sample7")); // Sample7 - Id, no Name or Path
}
}
} }

View File

@@ -1 +1,2 @@
Name: This is another test.txt Name: This is another test.txt
Description: But not really.

View File

@@ -0,0 +1,2 @@
Path: ThisIs.Sample4
Author: Bertrand Le Roy

View File

@@ -0,0 +1,3 @@
Name: AnotherSample
Path: Sample 5
Author: Some One

View File

@@ -0,0 +1,2 @@
Name: SampleSix
Author: Some One

View File

@@ -18,7 +18,9 @@ namespace Orchard.Tests.Mvc.Routes {
Feature =new Feature { Feature =new Feature {
Descriptor=new FeatureDescriptor { Descriptor=new FeatureDescriptor {
Extension=new ExtensionDescriptor { Extension=new ExtensionDescriptor {
Name="Foo" Id="Foo",
Name="A Foo Module",
Path="Foo"
} }
} }
} }
@@ -28,7 +30,9 @@ namespace Orchard.Tests.Mvc.Routes {
Feature =new Feature { Feature =new Feature {
Descriptor=new FeatureDescriptor { Descriptor=new FeatureDescriptor {
Extension=new ExtensionDescriptor { Extension=new ExtensionDescriptor {
Name="Bar" Id="Bar",
Name="Bar",
Path="BarBar"
} }
} }
} }
@@ -46,9 +50,9 @@ namespace Orchard.Tests.Mvc.Routes {
var fooRoute = routes.Select(x => x.Route).OfType<Route>() var fooRoute = routes.Select(x => x.Route).OfType<Route>()
.Single(x => x.Url == "Foo/{controller}/{action}/{id}"); .Single(x => x.Url == "Foo/{controller}/{action}/{id}");
var barAdmin = routes.Select(x => x.Route).OfType<Route>() var barAdmin = routes.Select(x => x.Route).OfType<Route>()
.Single(x => x.Url == "Admin/Bar/{action}/{id}"); .Single(x => x.Url == "Admin/BarBar/{action}/{id}");
var barRoute = routes.Select(x => x.Route).OfType<Route>() var barRoute = routes.Select(x => x.Route).OfType<Route>()
.Single(x => x.Url == "Bar/{controller}/{action}/{id}"); .Single(x => x.Url == "BarBar/{controller}/{action}/{id}");
Assert.That(fooAdmin.DataTokens["area"], Is.EqualTo("Long.Name.Foo")); Assert.That(fooAdmin.DataTokens["area"], Is.EqualTo("Long.Name.Foo"));
Assert.That(fooRoute.DataTokens["area"], Is.EqualTo("Long.Name.Foo")); Assert.That(fooRoute.DataTokens["area"], Is.EqualTo("Long.Name.Foo"));

View File

@@ -335,7 +335,18 @@
<Install>true</Install> <Install>true</Install>
</BootstrapperPackage> </BootstrapperPackage>
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup>
<EmbeddedResource Include="Environment\Extensions\FoldersData\Sample4\Module.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Environment\Extensions\FoldersData\Sample5\Module.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Environment\Extensions\FoldersData\Sample6\Module.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Environment\Extensions\FoldersData\Sample7\Module.txt" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.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.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -2,11 +2,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using Orchard.Caching; using Orchard.Caching;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.WebSite; using Orchard.FileSystems.WebSite;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Logging; using Orchard.Logging;
using Orchard.Utility.Extensions;
namespace Orchard.Environment.Extensions.Folders { namespace Orchard.Environment.Extensions.Folders {
public class ExtensionFolders : IExtensionFolders { public class ExtensionFolders : IExtensionFolders {
@@ -49,8 +51,24 @@ namespace Orchard.Environment.Extensions.Folders {
var manifestPath = Path.Combine(subfolderPath, _manifestName); var manifestPath = Path.Combine(subfolderPath, _manifestName);
try { try {
var descriptor = GetExtensionDescriptor(path, extensionId, manifestPath); var descriptor = GetExtensionDescriptor(path, extensionId, manifestPath);
if (descriptor != null)
localList.Add(descriptor); if (descriptor == null)
continue;
if (descriptor.Path != null && !descriptor.Path.IsValidUrlSegment()) {
Logger.Error("The module '{0}' could not be loaded because it has an invalid Path ({1}). It was ignored. The Path if specified must be a valid URL segment. The best bet is to stick with letters and numbers with no spaces.",
extensionId,
descriptor.Path);
continue;
}
if (descriptor.Path == null) {
descriptor.Path = descriptor.Name.IsValidUrlSegment()
? descriptor.Name
: descriptor.Id;
}
localList.Add(descriptor);
} }
catch (Exception ex) { catch (Exception ex) {
// Ignore invalid module manifests // Ignore invalid module manifests
@@ -72,6 +90,7 @@ namespace Orchard.Environment.Extensions.Folders {
Id = extensionId, Id = extensionId,
ExtensionType = extensionType, ExtensionType = extensionType,
Name = GetValue(manifest, "Name") ?? extensionId, Name = GetValue(manifest, "Name") ?? extensionId,
Path = GetValue(manifest, "Path"),
Description = GetValue(manifest, "Description"), Description = GetValue(manifest, "Description"),
Version = GetValue(manifest, "Version"), Version = GetValue(manifest, "Version"),
OrchardVersion = GetValue(manifest, "OrchardVersion"), OrchardVersion = GetValue(manifest, "OrchardVersion"),
@@ -125,6 +144,9 @@ namespace Orchard.Environment.Extensions.Folders {
case "Name": case "Name":
manifest.Add("Name", field[1]); manifest.Add("Name", field[1]);
break; break;
case "Path":
manifest.Add("Path", field[1]);
break;
case "Description": case "Description":
manifest.Add("Description", field[1]); manifest.Add("Description", field[1]);
break; break;

View File

@@ -19,6 +19,7 @@ namespace Orchard.Environment.Extensions.Models {
// extension metadata // extension metadata
public string Name { get; set; } public string Name { get; set; }
public string Path { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Version { get; set; } public string Version { get; set; }
public string OrchardVersion { get; set; } public string OrchardVersion { get; set; }

View File

@@ -13,18 +13,18 @@ namespace Orchard.Mvc.Routes {
} }
public IEnumerable<RouteDescriptor> GetRoutes() { public IEnumerable<RouteDescriptor> GetRoutes() {
var displayNamesPerArea = _blueprint.Controllers.GroupBy( var displayPathsPerArea = _blueprint.Controllers.GroupBy(
x => x.AreaName, x => x.AreaName,
x => x.Feature.Descriptor.Extension.Name); x => x.Feature.Descriptor.Extension.Path);
foreach (var item in displayNamesPerArea) { foreach (var item in displayPathsPerArea) {
var areaName = item.Key; var areaName = item.Key;
var displayName = item.Distinct().Single(); var displayPath = item.Distinct().Single();
yield return new RouteDescriptor { yield return new RouteDescriptor {
Priority = -10, Priority = -10,
Route = new Route( Route = new Route(
"Admin/" + displayName + "/{action}/{id}", "Admin/" + displayPath + "/{action}/{id}",
new RouteValueDictionary { new RouteValueDictionary {
{"area", areaName}, {"area", areaName},
{"controller", "admin"}, {"controller", "admin"},
@@ -40,7 +40,7 @@ namespace Orchard.Mvc.Routes {
yield return new RouteDescriptor { yield return new RouteDescriptor {
Priority = -10, Priority = -10,
Route = new Route( Route = new Route(
displayName + "/{controller}/{action}/{id}", displayPath + "/{controller}/{action}/{id}",
new RouteValueDictionary { new RouteValueDictionary {
{"area", areaName}, {"area", areaName},
{"controller", "home"}, {"controller", "home"},

View File

@@ -67,5 +67,20 @@ namespace Orchard.Utility.Extensions {
Select(x => Convert.ToByte(hex.Substring(x, 2), 16)). Select(x => Convert.ToByte(hex.Substring(x, 2), 16)).
ToArray(); ToArray();
} }
public static bool IsValidUrlSegment(this string segment) {
// valid isegment from rfc3987 - http://tools.ietf.org/html/rfc3987#page-8
// the relevant bits:
// isegment = *ipchar
// ipchar = iunreserved / pct-encoded / sub-delims / ":" / "@"
// iunreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" / ucschar
// pct-encoded = "%" HEXDIG HEXDIG
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
// ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD / %xD0000-DFFFD / %xE1000-EFFFD
//
// rough blacklist regex == m/^[^/?#[]@"^{}|\s`<>]+$/ (leaving off % to keep the regex simple)
return Regex.IsMatch(segment, @"^[^/?#[\]@""^{}|`<>\s]+$");
}
} }
} }