Fixed help files.

This commit is contained in:
Dario Solera 2009-09-30 13:47:13 +00:00
commit b8f912cc79
1543 changed files with 395123 additions and 0 deletions

View file

@ -0,0 +1,43 @@

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
namespace ScrewTurn.Wiki.AclEngine.Tests {
[TestFixture]
public class AclChangedEventArgsTests {
[Test]
public void Constructor() {
AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant);
AclChangedEventArgs args = new AclChangedEventArgs(new AclEntry[] { entry }, Change.EntryStored);
Assert.AreEqual(1, args.Entries.Length, "Wrong entry count");
Assert.AreSame(entry, args.Entries[0], "Wrong Entry instance");
Assert.AreEqual(Change.EntryStored, args.Change, "Wrong change");
args = new AclChangedEventArgs(new AclEntry[] { entry }, Change.EntryDeleted);
Assert.AreEqual(1, args.Entries.Length, "Wrong entry count");
Assert.AreSame(entry, args.Entries[0], "Wrong Entry instance");
Assert.AreEqual(Change.EntryDeleted, args.Change, "Wrong change");
}
[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void Constructor_NullEntries() {
AclChangedEventArgs args = new AclChangedEventArgs(null, Change.EntryDeleted);
}
[Test]
[ExpectedException(typeof(ArgumentException))]
public void Constructor_EmptyEntries() {
AclChangedEventArgs args = new AclChangedEventArgs(new AclEntry[0], Change.EntryDeleted);
}
}
}

View file

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{9F22D0A6-115B-4EB1-8506-65263674CEA3}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ScrewTurn.Wiki.AclEngine.Tests</RootNamespace>
<AssemblyName>ScrewTurn.Wiki.AclEngine.Tests</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Reference Include="nunit.framework, Version=2.5.1.9189, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\References\Tools\NUnit\framework\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="Rhino.Mocks, Version=3.5.0.1337, Culture=neutral, PublicKeyToken=0b3305902db7183f, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\References\Tools\Rhino.Mocks\Rhino.Mocks.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\AssemblyVersion.cs">
<Link>AssemblyVersion.cs</Link>
</Compile>
<Compile Include="AclChangedEventArgsTests.cs" />
<Compile Include="AclEntryTests.cs" />
<Compile Include="AclEvaluatorTests.cs" />
<Compile Include="AclManagerBaseTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AclEngine\AclEngine.csproj">
<Project>{44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}</Project>
<Name>AclEngine</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -0,0 +1,77 @@

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
namespace ScrewTurn.Wiki.AclEngine.Tests {
[TestFixture]
public class AclEntryTests {
[Test]
public void Constructor() {
AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant);
Assert.AreEqual("Res", entry.Resource, "Wrong resource");
Assert.AreEqual("Action", entry.Action, "Wrong action");
Assert.AreEqual("U.User", entry.Subject, "Wrong subject");
Assert.AreEqual(Value.Grant, entry.Value, "Wrong value");
entry = new AclEntry("Res", "Action", "G.Group", Value.Deny);
Assert.AreEqual("Res", entry.Resource, "Wrong resource");
Assert.AreEqual("Action", entry.Action, "Wrong action");
Assert.AreEqual("G.Group", entry.Subject, "Wrong subject");
Assert.AreEqual(Value.Deny, entry.Value, "Wrong value");
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void Constructor_InvalidResource(string r) {
AclEntry entry = new AclEntry(r, "Action", "U.USer", Value.Grant);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void Constructor_InvalidAction(string a) {
AclEntry entry = new AclEntry("Res", a, "G.Group", Value.Deny);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void Constructor_InvalidSubject(string s) {
AclEntry entry = new AclEntry("Res", "Action", s, Value.Grant);
}
[Test]
public void Equals() {
AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant);
Assert.IsFalse(entry.Equals(null), "Equals should return false (testing null)");
Assert.IsFalse(entry.Equals("blah"), "Equals should return false (testing a string)");
Assert.IsFalse(entry.Equals(new AclEntry("Res1", "Action", "U.User", Value.Grant)), "Equals should return false");
Assert.IsFalse(entry.Equals(new AclEntry("Res", "Action1", "U.User", Value.Grant)), "Equals should return false");
Assert.IsFalse(entry.Equals(new AclEntry("Res", "Action", "U.User1", Value.Grant)), "Equals should return false");
Assert.IsTrue(entry.Equals(new AclEntry("Res", "Action", "U.User", Value.Deny)), "Equals should return true");
Assert.IsTrue(entry.Equals(new AclEntry("Res", "Action", "U.User", Value.Grant)), "Equals should return true");
Assert.IsTrue(entry.Equals(entry), "Equals should return true");
}
[Test]
public void Static_Equals() {
AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant);
Assert.IsFalse(AclEntry.Equals(entry, null), "Equals should return false (testing null)");
Assert.IsFalse(AclEntry.Equals(entry, "blah"), "Equals should return false (testing a string)");
Assert.IsFalse(AclEntry.Equals(entry, new AclEntry("Res1", "Action", "U.User", Value.Grant)), "Equals should return false");
Assert.IsFalse(AclEntry.Equals(entry, new AclEntry("Res", "Action1", "U.User", Value.Grant)), "Equals should return false");
Assert.IsFalse(AclEntry.Equals(entry, new AclEntry("Res", "Action", "U.User1", Value.Grant)), "Equals should return false");
Assert.IsTrue(AclEntry.Equals(entry, new AclEntry("Res", "Action", "U.User", Value.Deny)), "Equals should return true");
Assert.IsTrue(AclEntry.Equals(entry, new AclEntry("Res", "Action", "U.User", Value.Grant)), "Equals should return true");
Assert.IsTrue(AclEntry.Equals(entry, entry), "Equals should return true");
}
}
}

View file

@ -0,0 +1,440 @@

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
namespace ScrewTurn.Wiki.AclEngine.Tests {
[TestFixture]
public class AclEvaluatorTests {
[Test]
public void AuthorizeAction_InexistentResource() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny));
entries.Add(new AclEntry("Res", "Action2", "U.User", Value.Deny));
entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant));
entries.Add(new AclEntry("Res2", "Action", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Unknown, AclEvaluator.AuthorizeAction("Res3", "Action", "U.User", new string[0], entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_InexistentAction() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny));
entries.Add(new AclEntry("Res", "Action2", "U.User", Value.Deny));
entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant));
entries.Add(new AclEntry("Res2", "Action", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Unknown, AclEvaluator.AuthorizeAction("Res", "Action3", "U.User", new string[0], entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantUserExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny));
entries.Add(new AclEntry("Res", "Action2", "U.User", Value.Deny));
entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant));
entries.Add(new AclEntry("Res2", "Action", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[0], entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantUserFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "U.User", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny));
entries.Add(new AclEntry("Res", "Action2", "U.User", Value.Deny));
entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant));
entries.Add(new AclEntry("Res2", "Action", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[0], entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyUserExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny));
entries.Add(new AclEntry("Res", "Action2", "U.User", Value.Deny));
entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant));
entries.Add(new AclEntry("Res2", "Action", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User2", new string[0], entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyUserFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant));
entries.Add(new AclEntry("Res", "*", "U.User2", Value.Deny));
entries.Add(new AclEntry("Res", "Action2", "U.User", Value.Deny));
entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant));
entries.Add(new AclEntry("Res2", "Action", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User2", new string[0], entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantGroupExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny));
entries.Add(new AclEntry("Res", "Action2", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res2", "Action", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantGroupFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny));
entries.Add(new AclEntry("Res", "Action2", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res2", "Action", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyGroupExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Grant));
entries.Add(new AclEntry("Res", "Action2", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res2", "Action", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "*", "U.User3", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyGroupFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Grant));
entries.Add(new AclEntry("Res", "Action2", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res2", "Action", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "*", "U.User3", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantGroupFullControl_DenyUserFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "*", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantGroupExplicit_DenyUserFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "*", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantGroupFullControl_DenyUserExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyGroupFullControl_GrantUserFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "*", "U.User", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyGroupExplicit_GrantUserFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "*", "U.User", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyGroupFullControl_GrantUserExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantGroupFullControl_GrantUserFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "*", "U.User", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantGroupExplicit_GrantUserExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyGroupExplicit_GrantUserExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantGroupFullControl_GrantUserExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantGroupExplicit_GrantUserFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "*", "U.User", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantGroupExplicit_DenyUserExpicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyGroupExplicit_DenyUserExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyGroupFullControl_DenyUserFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "*", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyGroupFullControl_DenyUserExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyGroupExplicit_DenyUserFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "*", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantOneGroupExplicit_GrantOtherGroupExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group1", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "G.Group2", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyOneGroupExplicit_GrantOtherGroupExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group1", Value.Deny));
entries.Add(new AclEntry("Res", "Action", "G.Group2", Value.Grant));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyOneGroupExplicit_DenyOtherGroupExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group1", Value.Deny));
entries.Add(new AclEntry("Res", "Action", "G.Group2", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantOneGroupFullControl_GrantOtherGroupFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group1", Value.Grant));
entries.Add(new AclEntry("Res", "*", "G.Group2", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyOneGroupFullControl_DenyOtherGroupFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group1", Value.Deny));
entries.Add(new AclEntry("Res", "*", "G.Group2", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyOneGroupFullControl_GrantOtherGroupFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group1", Value.Grant));
entries.Add(new AclEntry("Res", "*", "G.Group2", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyOneGroupFullControl_GrantOtherGroupExplicit() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "G.Group1", Value.Grant));
entries.Add(new AclEntry("Res", "*", "G.Group2", Value.Deny));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyOneGroupExplicit_GrantOtherGroupFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group1", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "G.Group2", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantOneGroupExplicit_GrantOtherGroupFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group1", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "G.Group2", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyOneGroupExplicit_DenyOtherGroupFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group1", Value.Deny));
entries.Add(new AclEntry("Res", "Action", "G.Group2", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyUserExplicit_GrantUserFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "U.User", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[0], entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_DenyGroupExplicit_GrantGroupFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny));
Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantUserExplicit_DenyUserFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "U.User", Value.Deny));
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[0], entries.ToArray()), "Wrong auth result");
}
[Test]
public void AuthorizeAction_GrantGroupExplicit_DenyGroupFullControl() {
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "*", "G.Group", Value.Deny));
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Grant));
Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result");
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void AuthorizeAction_InvalidResource(string r) {
AclEvaluator.AuthorizeAction(r, "Action", "U.User", new string[0], new AclEntry[0]);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
[TestCase(AclEntry.FullControlAction, ExpectedException = typeof(ArgumentException))]
public void AuthorizeAction_InvalidAction(string a) {
AclEvaluator.AuthorizeAction("Res", a, "U.User", new string[0], new AclEntry[0]);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void AuthorizeAction_InvalidUser(string u) {
AclEvaluator.AuthorizeAction("Res", "Action", u, new string[0], new AclEntry[0]);
}
[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void AuthorizeAction_NullGroups() {
AclEvaluator.AuthorizeAction("Res", "Action", "U.User", null, new AclEntry[0]);
}
[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void AuthorizeAction_NullEntries() {
AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[0], null);
}
[Test]
public void AuthorizeAction_EmptyEntries() {
Assert.AreEqual(Authorization.Unknown, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[0], new AclEntry[0]), "Wrong auth result");
}
}
}

View file

@ -0,0 +1,498 @@

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using Rhino.Mocks;
namespace ScrewTurn.Wiki.AclEngine.Tests {
[TestFixture]
public class AclManagerBaseTests {
private MockRepository mocks = new MockRepository();
private AclManagerBase MockAclManager() {
AclManagerBase manager = mocks.DynamicMock<AclManagerBase>();
return manager;
}
private void AssertAclEntriesAreEqual(AclEntry expected, AclEntry actual) {
Assert.AreEqual(expected.Resource, actual.Resource, "Wrong resource");
Assert.AreEqual(expected.Action, actual.Action, "Wrong action");
Assert.AreEqual(expected.Subject, actual.Subject, "Wrong subject");
Assert.AreEqual(expected.Value, actual.Value, "Wrong value");
}
[Test]
public void StoreEntry_RetrieveAllEntries() {
AclManagerBase manager = MockAclManager();
Assert.AreEqual(0, manager.TotalEntries, "Wrong initial entry count");
Assert.AreEqual(0, manager.RetrieveAllEntries().Length, "Wrong initial entry count");
Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Deny), "StoreEntry should return true");
Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count");
AclEntry[] allEntries = manager.RetrieveAllEntries();
Assert.AreEqual(2, allEntries.Length, "Wrong entry count");
Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); });
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "G.Group", Value.Deny), allEntries[0]);
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Grant), allEntries[1]);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void StoreEntry_InvalidResource(string s) {
AclManagerBase manager = MockAclManager();
manager.StoreEntry(s, "Action", "U.User", Value.Grant);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void StoreEntry_InvalidAction(string a) {
AclManagerBase manager = MockAclManager();
manager.StoreEntry("Res", a, "U.User", Value.Grant);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void StoreEntry_InvalidSubject(string s) {
AclManagerBase manager = MockAclManager();
manager.StoreEntry("Res", "Action", s, Value.Grant);
}
[Test]
public void StoreEntry_Overwrite() {
AclManagerBase manager = MockAclManager();
Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Grant), "StoreEntry should return true");
// Overwrite with a deny
Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Deny), "StoreEntry should return true");
Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count");
AclEntry[] allEntries = manager.RetrieveAllEntries();
Assert.AreEqual(2, allEntries.Length, "Wrong entry count");
Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); });
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "G.Group", Value.Grant), allEntries[0]);
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Deny), allEntries[1]);
}
[Test]
public void DeleteEntry_RetrieveAllEntries() {
AclManagerBase manager = MockAclManager();
Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Deny), "StoreEntry should return true");
Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count");
Assert.IsFalse(manager.DeleteEntry("Res1", "Action", "G.Group"), "DeleteEntry should return false");
Assert.IsFalse(manager.DeleteEntry("Res", "Action1", "G.Group"), "DeleteEntry should return false");
Assert.IsFalse(manager.DeleteEntry("Res", "Action", "G.Group1"), "DeleteEntry should return false");
Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count");
Assert.IsTrue(manager.DeleteEntry("Res", "Action", "G.Group"), "DeleteEntry should return true");
Assert.AreEqual(1, manager.TotalEntries, "Wrong entry count");
AclEntry[] allEntries = manager.RetrieveAllEntries();
Assert.AreEqual(1, allEntries.Length, "Wrong entry count");
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Grant), allEntries[0]);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void DeleteEntry_InvalidResource(string r) {
AclManagerBase manager = MockAclManager();
manager.DeleteEntry(r, "Action", "U.User");
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void DeleteEntry_InvalidAction(string a) {
AclManagerBase manager = MockAclManager();
manager.DeleteEntry("Res", a, "U.User");
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void DeleteEntry_InvalidSubject(string s) {
AclManagerBase manager = MockAclManager();
manager.DeleteEntry("Res", "Action", s);
}
[Test]
public void DeleteEntriesForResource_RetrieveAllEntries() {
AclManagerBase manager = MockAclManager();
Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Deny), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res2", "Action2", "G.Group2", Value.Grant), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res2", "Action3", "U.User", Value.Deny), "StoreEntry should return true");
Assert.AreEqual(4, manager.TotalEntries, "Wrong entry count");
Assert.IsFalse(manager.DeleteEntriesForResource("Inexistent"), "DeleteEntriesForResource should return false");
Assert.AreEqual(4, manager.TotalEntries, "Wrong entry count");
Assert.IsTrue(manager.DeleteEntriesForResource("Res2"), "DeleteEntriesForResource should return true");
Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count");
AclEntry[] allEntries = manager.RetrieveAllEntries();
Assert.AreEqual(2, allEntries.Length, "Wrong entry count");
Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); });
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "G.Group", Value.Deny), allEntries[0]);
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Grant), allEntries[1]);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void DeleteEntriesForResource_InvalidResource(string r) {
AclManagerBase manager = MockAclManager();
manager.DeleteEntriesForResource(r);
}
[Test]
public void DeleteEntriesForSubject_RetrieveAllEntries() {
AclManagerBase manager = MockAclManager();
Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Deny), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res2", "Action2", "G.Group2", Value.Grant), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res2", "Action3", "G.Group2", Value.Deny), "StoreEntry should return true");
Assert.AreEqual(4, manager.TotalEntries, "Wrong entry count");
Assert.IsFalse(manager.DeleteEntriesForSubject("I.Inexistent"), "DeleteEntriesForSubject should return false");
Assert.AreEqual(4, manager.TotalEntries, "Wrong entry count");
Assert.IsTrue(manager.DeleteEntriesForSubject("G.Group2"), "DeleteEntriesForSubject should return true");
Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count");
AclEntry[] allEntries = manager.RetrieveAllEntries();
Assert.AreEqual(2, allEntries.Length, "Wrong entry count");
Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); });
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "G.Group", Value.Deny), allEntries[0]);
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Grant), allEntries[1]);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void DeleteEntriesForSubject_InvalidSubject(string s) {
AclManagerBase manager = MockAclManager();
manager.DeleteEntriesForSubject(s);
}
[Test]
public void RetrieveEntriesForResource() {
AclManagerBase manager = MockAclManager();
Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Deny), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res2", "Action2", "G.Group2", Value.Grant), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res2", "Action3", "G.Group2", Value.Deny), "StoreEntry should return true");
Assert.AreEqual(0, manager.RetrieveEntriesForResource("Inexistent").Length, "Wrong result count");
AclEntry[] allEntries = manager.RetrieveEntriesForResource("Res");
Assert.AreEqual(2, allEntries.Length, "Wrong entry count");
Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); });
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "G.Group", Value.Deny), allEntries[0]);
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Grant), allEntries[1]);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void RetrieveEntriesForResource_InvalidResource(string r) {
AclManagerBase manager = MockAclManager();
manager.RetrieveEntriesForResource(r);
}
[Test]
public void RetrieveEntriesForSubject() {
AclManagerBase manager = MockAclManager();
Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Deny), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res2", "Action2", "G.Group2", Value.Grant), "StoreEntry should return true");
Assert.IsTrue(manager.StoreEntry("Res2", "Action3", "G.Group2", Value.Deny), "StoreEntry should return true");
Assert.AreEqual(0, manager.RetrieveEntriesForSubject("I.Inexistent").Length, "Wrong result count");
AclEntry[] allEntries = manager.RetrieveEntriesForSubject("G.Group2");
Assert.AreEqual(2, allEntries.Length, "Wrong entry count");
Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Action.CompareTo(y.Action); });
AssertAclEntriesAreEqual(new AclEntry("Res2", "Action2", "G.Group2", Value.Grant), allEntries[0]);
AssertAclEntriesAreEqual(new AclEntry("Res2", "Action3", "G.Group2", Value.Deny), allEntries[1]);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void RetrieveEntriesForSubject_InvalidSubject(string s) {
AclManagerBase manager = MockAclManager();
manager.RetrieveEntriesForSubject(s);
}
[Test]
public void InitializeData() {
AclManagerBase manager = MockAclManager();
List<AclEntry> entries = new List<AclEntry>();
entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant));
entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny));
manager.InitializeData(entries.ToArray());
Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count");
AclEntry[] allEntries = manager.RetrieveAllEntries();
Assert.AreEqual(2, allEntries.Length, "Wrong entry count");
Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); });
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "G.Group", Value.Deny), allEntries[0]);
AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Grant), allEntries[1]);
}
[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void InitializeData_NullData() {
AclManagerBase manager = MockAclManager();
manager.InitializeData(null);
}
[Test]
public void Event_AclChanged_StoreEntry() {
AclManagerBase manager = MockAclManager();
AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant);
bool invoked = false;
manager.AclChanged += delegate(object sender, AclChangedEventArgs e) {
invoked = true;
Assert.AreEqual(1, e.Entries.Length, "Wrong entry count");
AssertAclEntriesAreEqual(entry, e.Entries[0]);
Assert.AreEqual(Change.EntryStored, e.Change);
};
manager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value);
Assert.IsTrue(invoked, "Store event not invoked");
}
[Test]
public void Event_AclChanged_OverwriteEntry() {
AclManagerBase manager = MockAclManager();
AclEntry entryOld = new AclEntry("Res", "Action", "U.User", Value.Deny);
AclEntry entryNew = new AclEntry("Res", "Action", "U.User", Value.Grant);
manager.StoreEntry(entryOld.Resource, entryOld.Action, entryOld.Subject, entryOld.Value);
bool invokedStore = false;
bool invokedDelete = false;
manager.AclChanged += delegate(object sender, AclChangedEventArgs e) {
if(e.Change == Change.EntryStored) {
invokedStore = true;
Assert.AreEqual(1, e.Entries.Length, "Wrong entry count");
AssertAclEntriesAreEqual(entryNew, e.Entries[0]);
Assert.AreEqual(Change.EntryStored, e.Change);
}
else {
invokedDelete = true;
Assert.AreEqual(1, e.Entries.Length, "Wrong entry count");
AssertAclEntriesAreEqual(entryOld, e.Entries[0]);
Assert.AreEqual(Change.EntryDeleted, e.Change);
}
};
manager.StoreEntry(entryNew.Resource, entryNew.Action, entryNew.Subject, entryNew.Value);
Assert.IsTrue(invokedStore, "Store event not invoked");
Assert.IsTrue(invokedDelete, "Delete event not invoked");
}
[Test]
public void Event_AclChanged_DeleteEntry() {
AclManagerBase manager = MockAclManager();
AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant);
manager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value);
bool invokedDelete = false;
manager.AclChanged += delegate(object sender, AclChangedEventArgs e) {
invokedDelete = true;
Assert.AreEqual(1, e.Entries.Length, "Wrong entry count");
AssertAclEntriesAreEqual(entry, e.Entries[0]);
Assert.AreEqual(Change.EntryDeleted, e.Change, "Wrong change");
};
manager.DeleteEntry(entry.Resource, entry.Action, entry.Subject);
Assert.IsTrue(invokedDelete, "Delete event not invoked");
}
[Test]
public void Event_AclChanged_DeleteEntriesForResource() {
AclManagerBase manager = MockAclManager();
AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant);
manager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value);
manager.StoreEntry("Res2", "Action", "G.Group", Value.Deny);
bool invokedDelete = false;
manager.AclChanged += delegate(object sender, AclChangedEventArgs e) {
invokedDelete = true;
Assert.AreEqual(1, e.Entries.Length, "Wrong entry count");
AssertAclEntriesAreEqual(entry, e.Entries[0]);
Assert.AreEqual(Change.EntryDeleted, e.Change, "Wrong change");
};
manager.DeleteEntriesForResource(entry.Resource);
Assert.IsTrue(invokedDelete, "Delete event not invoked");
}
[Test]
public void Event_AclChanged_DeleteEntriesForSubject() {
AclManagerBase manager = MockAclManager();
AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant);
manager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value);
manager.StoreEntry("Res2", "Action", "G.Group", Value.Deny);
bool invokedDelete = false;
manager.AclChanged += delegate(object sender, AclChangedEventArgs e) {
invokedDelete = true;
Assert.AreEqual(1, e.Entries.Length, "Wrong entry count");
AssertAclEntriesAreEqual(entry, e.Entries[0]);
Assert.AreEqual(Change.EntryDeleted, e.Change, "Wrong change");
};
manager.DeleteEntriesForSubject(entry.Subject);
Assert.IsTrue(invokedDelete, "Delete event not invoked");
}
[Test]
public void RenameResource() {
AclManagerBase manager = MockAclManager();
Assert.IsFalse(manager.RenameResource("Res", "Res_Renamed"), "RenameResource should return false");
AclEntry entry1 = new AclEntry("Res", "Action", "U.User", Value.Grant);
AclEntry newEntry1 = new AclEntry("Res_Renamed", "Action", "U.User", Value.Grant);
AclEntry entry2 = new AclEntry("Res", "Action2", "U.User2", Value.Deny);
AclEntry newEntry2 = new AclEntry("Res_Renamed", "Action2", "U.User", Value.Deny);
manager.StoreEntry(entry1.Resource, entry1.Action, entry1.Subject, entry1.Value);
manager.StoreEntry(entry2.Resource, entry2.Action, entry2.Subject, entry2.Value);
manager.StoreEntry("Res2", "Action", "G.Group", Value.Deny);
bool invokedDelete1 = false;
bool invokedStore1 = false;
bool invokedDelete2 = false;
bool invokedStore2 = false;
manager.AclChanged += delegate(object sender, AclChangedEventArgs e) {
if(e.Change == Change.EntryDeleted) {
Assert.AreEqual(1, e.Entries.Length, "Wrong entry count");
Assert.AreEqual("Res", e.Entries[0].Resource, "Wrong resource");
if(e.Entries[0].Action == entry1.Action) invokedDelete1 = true;
if(e.Entries[0].Action == entry2.Action) invokedDelete2 = true;
}
else {
Assert.AreEqual(1, e.Entries.Length, "Wrong entry count");
Assert.AreEqual("Res_Renamed", e.Entries[0].Resource, "Wrong resource");
if(e.Entries[0].Action == entry1.Action) invokedStore1 = true;
if(e.Entries[0].Action == entry2.Action) invokedStore2 = true;
}
};
Assert.IsTrue(manager.RenameResource("Res", "Res_Renamed"), "RenameResource should return true");
Assert.IsTrue(invokedDelete1, "Delete event 1 not invoked");
Assert.IsTrue(invokedStore1, "Store event 1 not invoked");
Assert.IsTrue(invokedDelete2, "Delete event 2 not invoked");
Assert.IsTrue(invokedStore2, "Store event 2 not invoked");
AclEntry[] entries = manager.RetrieveAllEntries();
Assert.AreEqual(3, entries.Length, "Wrong entry count");
Array.Sort(entries, delegate(AclEntry x, AclEntry y) { return x.Resource.CompareTo(y.Resource); });
Assert.AreEqual("Res_Renamed", entries[0].Resource, "Wrong resource");
if(entries[0].Value == Value.Grant) {
Assert.AreEqual("Action", entries[0].Action, "Wrong action");
Assert.AreEqual("U.User", entries[0].Subject, "Wrong subject");
}
else {
Assert.AreEqual("Action2", entries[0].Action, "Wrong action");
Assert.AreEqual("U.User2", entries[0].Subject, "Wrong subject");
}
Assert.AreEqual("Res_Renamed", entries[1].Resource, "Wrong resource");
if(entries[1].Value == Value.Grant) {
Assert.AreEqual("Action", entries[1].Action, "Wrong action");
Assert.AreEqual("U.User", entries[1].Subject, "Wrong subject");
}
else {
Assert.AreEqual("Action2", entries[1].Action, "Wrong action");
Assert.AreEqual("U.User2", entries[1].Subject, "Wrong subject");
}
Assert.AreEqual("Res2", entries[2].Resource, "Wrong resource");
Assert.AreEqual("Action", entries[2].Action, "Wrong action");
Assert.AreEqual("G.Group", entries[2].Subject, "Wrong subject");
Assert.AreEqual(Value.Deny, entries[2].Value, "Wrong value");
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void RenameResource_InvalidResource(string r) {
AclManagerBase manager = MockAclManager();
manager.RenameResource(r, "new_name");
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void RenameResource_InvalidNewName(string n) {
AclManagerBase manager = MockAclManager();
manager.RenameResource("res", n);
}
}
}

View file

@ -0,0 +1,18 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ScrewTurn Wiki ACL Engine Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("aa6f62d5-284a-443f-a5c1-d75fb638a8b7")]

View file

@ -0,0 +1,61 @@

using System;
using System.Collections.Generic;
using System.Text;
namespace ScrewTurn.Wiki.AclEngine {
/// <summary>
/// Contains arguments for the <see cref="IAclManager.AclChanged" /> event.
/// </summary>
public class AclChangedEventArgs : EventArgs {
private AclEntry[] entries;
private Change change;
/// <summary>
/// Initializes a new instance of the <see cref="T:AclChangedEventArgs" /> class.
/// </summary>
/// <param name="entries">The entries that changed.</param>
/// <param name="change">The change.</param>
/// <exception cref="ArgumentNullException">If <paramref name="entries"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="entries"/> is empty.</exception>
public AclChangedEventArgs(AclEntry[] entries, Change change) {
if(entries == null) throw new ArgumentNullException("entry");
if(entries.Length == 0) throw new ArgumentException("Entries cannot be empty", "entries");
this.entries = entries;
this.change = change;
}
/// <summary>
/// Gets the entries that changed.
/// </summary>
public AclEntry[] Entries {
get { return entries; }
}
/// <summary>
/// Gets the change.
/// </summary>
public Change Change {
get { return change; }
}
}
/// <summary>
/// Lists legal changes for ACL entries.
/// </summary>
public enum Change {
/// <summary>
/// An entry is stored.
/// </summary>
EntryStored,
/// <summary>
/// An entry is deleted.
/// </summary>
EntryDeleted
}
}

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ScrewTurn.Wiki.AclEngine</RootNamespace>
<AssemblyName>ScrewTurn.Wiki.AclEngine</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DocumentationFile>bin\Debug\ScrewTurn.Wiki.AclEngine.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DocumentationFile>bin\Release\ScrewTurn.Wiki.AclEngine.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\AssemblyVersion.cs">
<Link>AssemblyVersion.cs</Link>
</Compile>
<Compile Include="AclEntry.cs" />
<Compile Include="AclChangedEventArgs.cs" />
<Compile Include="AclEvaluator.cs" />
<Compile Include="AclManagerBase.cs" />
<Compile Include="AclStorerBase.cs" />
<Compile Include="IAclManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

153
AclEngine/AclEntry.cs Normal file
View file

@ -0,0 +1,153 @@

using System;
using System.Collections.Generic;
using System.Text;
namespace ScrewTurn.Wiki.AclEngine {
/// <summary>
/// Represents an ACL Entry.
/// </summary>
public class AclEntry {
/// <summary>
/// The full control action.
/// </summary>
public const string FullControlAction = "*";
/// <summary>
/// The controlled resource.
/// </summary>
private string resource;
/// <summary>
/// The controlled action on the resource.
/// </summary>
private string action;
/// <summary>
/// The subject whose access to the resource/action is controlled.
/// </summary>
private string subject;
/// <summary>
/// The entry value.
/// </summary>
private Value value;
/// <summary>
/// Initializes a new instance of the <see cref="T:AclEntry" /> class.
/// </summary>
/// <param name="resource">The controlled resource.</param>
/// <param name="action">The controlled action on the resource.</param>
/// <param name="subject">The subject whose access to the resource/action is controlled.</param>
/// <param name="value">The entry value.</param>
/// <exception cref="ArgumentNullException">If <paramref name="resource"/>, <paramref name="action"/> or <paramref name="subject"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="resource"/>, <paramref name="action"/> or <paramref name="subject"/> are empty.</exception>
public AclEntry(string resource, string action, string subject, Value value) {
if(resource == null) throw new ArgumentNullException("resource");
if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource");
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(subject == null) throw new ArgumentNullException("subject");
if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject");
this.resource = resource;
this.action = action;
this.subject = subject;
this.value = value;
}
/// <summary>
/// Gets the controlled resource.
/// </summary>
public string Resource {
get { return resource; }
}
/// <summary>
/// Gets the controlled action on the resource.
/// </summary>
public string Action {
get { return action; }
}
/// <summary>
/// Gets the subject of the entry.
/// </summary>
public string Subject {
get { return subject; }
}
/// <summary>
/// Gets the value of the entry.
/// </summary>
public Value Value {
get { return value; }
}
/// <summary>
/// Gets a string representation of the current object.
/// </summary>
/// <returns>The string representation.</returns>
public override string ToString() {
return resource + "->" + action + ": " + subject + " (" + value.ToString() + ")";
}
/// <summary>
/// Gets a hash code for the current object.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode() {
return base.GetHashCode();
}
/// <summary>
/// Determines whether this object equals another (by value).
/// </summary>
/// <param name="obj">The other object.</param>
/// <returns><c>true</c> if this object equals <b>obj</b>, <c>false</c> otherwise.</returns>
public override bool Equals(object obj) {
AclEntry other = obj as AclEntry;
if(other != null) return Equals(other);
else return false;
}
/// <summary>
/// Determines whether this instance equals another (by value).
/// </summary>
/// <param name="other">The other instance.</param>
/// <returns><c>true</c> if this instance equals <b>other</b>, <c>false</c> otherwise.</returns>
public bool Equals(AclEntry other) {
if(object.ReferenceEquals(other, null)) return false;
else return resource == other.Resource &&
action == other.Action && subject == other.Subject;
}
/// <summary>
/// Determines whether two instances of <see cref="T:AclEntry" /> are equal (by value).
/// </summary>
/// <param name="x">The first instance.</param>
/// <param name="y">The second instance.</param>
/// <returns><c>true</c> if <b>x</b> equals <b>y</b>, <c>false</c> otherwise.</returns>
public static bool Equals(AclEntry x, AclEntry y) {
if(object.ReferenceEquals(x, null) && !object.ReferenceEquals(x, null)) return false;
if(!object.ReferenceEquals(x, null) && object.ReferenceEquals(x, null)) return false;
if(object.ReferenceEquals(x, null) && object.ReferenceEquals(x, null)) return true;
return x.Equals(y);
}
}
/// <summary>
/// Lists legal ACL Entry values.
/// </summary>
public enum Value {
/// <summary>
/// Deny the action.
/// </summary>
Deny = 0,
/// <summary>
/// Grant the action.
/// </summary>
Grant = 1
}
}

129
AclEngine/AclEvaluator.cs Normal file
View file

@ -0,0 +1,129 @@

using System;
using System.Collections.Generic;
using System.Text;
namespace ScrewTurn.Wiki.AclEngine {
/// <summary>
/// Implements tools for evaluating permissions.
/// </summary>
public static class AclEvaluator {
/// <summary>
/// Decides whether a user, member of some groups, is authorized to perform an action on a resource.
/// </summary>
/// <param name="resource">The resource.</param>
/// <param name="action">The action on the resource.</param>
/// <param name="user">The user, in the form 'U.Name'.</param>
/// <param name="groups">The groups the user is member of, in the form 'G.Name'.</param>
/// <param name="entries">The available ACL entries for the resource.</param>
/// <returns>The positive, negative, or indeterminate result.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resource"/>, <paramref name="action"/>, <paramref name="user"/>, <paramref name="groups"/> or <paramref name="entries"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="resource"/>, <paramref name="action"/>, <paramref name="user"/> are empty, or if <paramref name="action"/> equals <see cref="AclEntry.FullControlAction"/>.</exception>
public static Authorization AuthorizeAction(string resource, string action, string user, string[] groups, AclEntry[] entries) {
if(resource == null) throw new ArgumentNullException("resource");
if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource");
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(action == AclEntry.FullControlAction) throw new ArgumentException("Action cannot be the FullControl flag", "action");
if(user == null) throw new ArgumentNullException("user");
if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user");
if(groups == null) throw new ArgumentNullException("groups");
if(entries == null) throw new ArgumentNullException("entries");
// Simple ACL model
// Sort entries so that FullControl ones are at the bottom
// First look for an entry specific for the user
// If not found, look for a group that denies the permission
AclEntry[] sortedEntries = new AclEntry[entries.Length];
Array.Copy(entries, sortedEntries, entries.Length);
Array.Sort(sortedEntries, delegate(AclEntry x, AclEntry y) {
return x.Action.CompareTo(y.Action);
});
Array.Reverse(sortedEntries);
foreach(AclEntry entry in sortedEntries) {
if(entry.Resource == resource && (entry.Action == action || entry.Action == AclEntry.FullControlAction) && entry.Subject == user) {
if(entry.Value == Value.Grant) return Authorization.Granted;
else if(entry.Value == Value.Deny) return Authorization.Denied;
else throw new NotSupportedException("Entry value not supported");
}
}
// For each group, a decision is made
Dictionary<string, bool> groupFullControlGrant = new Dictionary<string, bool>();
Dictionary<string, bool> groupExplicitGrant = new Dictionary<string, bool>();
Dictionary<string, bool> groupFullControlDeny = new Dictionary<string, bool>();
foreach(string group in groups) {
foreach(AclEntry entry in entries) {
if(entry.Resource == resource && entry.Subject == group) {
if(!groupFullControlGrant.ContainsKey(group)) {
groupFullControlGrant.Add(group, false);
groupExplicitGrant.Add(group, false);
groupFullControlDeny.Add(group, false);
}
if(entry.Action == action) {
// Explicit action
if(entry.Value == Value.Grant) {
// An explicit grant only wins if there are no other explicit deny
groupExplicitGrant[group] = true;
}
else if(entry.Value == Value.Deny) {
// An explicit deny wins over all other entries
return Authorization.Denied;
}
}
else if(entry.Action == AclEntry.FullControlAction) {
// Full control, lower priority
if(entry.Value == Value.Deny) {
groupFullControlDeny[group] = true;
}
else if(entry.Value == Value.Grant) {
groupFullControlGrant[group] = true;
}
}
}
}
}
// Any explicit grant found at this step wins, because all explicit deny have been processed previously
bool tentativeGrant = false;
bool tentativeDeny = false;
foreach(string group in groupFullControlGrant.Keys) {
if(groupExplicitGrant[group]) return Authorization.Granted;
if(groupFullControlGrant[group] && !groupFullControlDeny[group]) tentativeGrant = true;
if(!groupFullControlGrant[group] && groupFullControlDeny[group]) tentativeDeny = true;
}
if(tentativeGrant && !tentativeDeny) return Authorization.Granted;
else if(tentativeDeny) return Authorization.Denied;
else return Authorization.Unknown;
}
}
/// <summary>
/// Lists legal authorization values.
/// </summary>
public enum Authorization {
/// <summary>
/// Authorization granted.
/// </summary>
Granted,
/// <summary>
/// Authorization denied.
/// </summary>
Denied,
/// <summary>
/// No information available.
/// </summary>
Unknown
}
}

287
AclEngine/AclManagerBase.cs Normal file
View file

@ -0,0 +1,287 @@

using System;
using System.Collections.Generic;
using System.Text;
namespace ScrewTurn.Wiki.AclEngine {
/// <summary>
/// Implements a base class for an ACL Manager.
/// </summary>
/// <remarks>All instance and static members are <b>thread-safe</b>.</remarks>
public abstract class AclManagerBase : IAclManager {
private List<AclEntry> entries;
/// <summary>
/// Initializes a new instance of the <see cref="T:AclManagerBase" /> abstract class.
/// </summary>
public AclManagerBase() {
entries = new List<AclEntry>(100);
}
/// <summary>
/// Handles the invokation of <see cref="IAclManager.AclChanged" /> event.
/// </summary>
/// <param name="entries">The changed entries.</param>
/// <param name="change">The change.</param>
protected void OnAclChanged(AclEntry[] entries, Change change) {
if(AclChanged != null) {
AclChanged(this, new AclChangedEventArgs(entries, change));
}
}
/// <summary>
/// Stores a new ACL entry.
/// </summary>
/// <param name="resource">The controlled resource.</param>
/// <param name="action">The action on the controlled resource.</param>
/// <param name="subject">The subject whose access to the resource/action is controlled.</param>
/// <param name="value">The value of the entry.</param>
/// <returns><c>true</c> if the entry is stored, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resource"/>, <paramref name="action"/> or <paramref name="subject"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="resource"/>, <paramref name="action"/> or <paramref name="subject"/> are empty.</exception>
public bool StoreEntry(string resource, string action, string subject, Value value) {
if(resource == null) throw new ArgumentNullException("resource");
if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource");
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(subject == null) throw new ArgumentNullException("subject");
if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject");
AclEntry result = new AclEntry(resource, action, subject, value);
lock(this) {
int index = entries.FindIndex(delegate(AclEntry x) { return AclEntry.Equals(x, result); });
if(index >= 0) {
AclEntry removed = entries[index];
entries.RemoveAt(index);
OnAclChanged(new AclEntry[] { removed }, Change.EntryDeleted);
}
entries.Add(result);
OnAclChanged(new AclEntry[] { result }, Change.EntryStored);
}
return true;
}
/// <summary>
/// Deletes an ACL entry.
/// </summary>
/// <param name="resource">The controlled resource.</param>
/// <param name="action">The action on the controlled resource.</param>
/// <param name="subject">The subject whose access to the resource/action is controlled.</param>
/// <returns><c>true</c> if the entry is deleted, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resource"/>, <paramref name="action"/> or <paramref name="subject"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="resource"/>, <paramref name="action"/> or <paramref name="subject"/> are empty.</exception>
public bool DeleteEntry(string resource, string action, string subject) {
if(resource == null) throw new ArgumentNullException("resource");
if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource");
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(subject == null) throw new ArgumentNullException("subject");
if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject");
AclEntry result = new AclEntry(resource, action, subject, Value.Deny);
lock(this) {
int index = entries.FindIndex(delegate(AclEntry x) { return AclEntry.Equals(x, result); });
if(index >= 0) {
AclEntry entry = entries[index];
entries.RemoveAt(index);
OnAclChanged(new AclEntry[] { entry }, Change.EntryDeleted);
return true;
}
else return false;
}
}
/// <summary>
/// Deletes all the ACL entries for a resource.
/// </summary>
/// <param name="resource">The controlled resource.</param>
/// <returns><c>true</c> if the entries are deleted, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resource"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="resource"/> is empty.</exception>
public bool DeleteEntriesForResource(string resource) {
if(resource == null) throw new ArgumentNullException("resource");
if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource");
lock(this) {
List<int> indexesToRemove = new List<int>(30);
List<AclEntry> entriesToRemove = new List<AclEntry>(30);
for(int i = 0; i < entries.Count; i++) {
if(entries[i].Resource == resource) {
indexesToRemove.Add(i);
entriesToRemove.Add(entries[i]);
}
}
if(indexesToRemove.Count > 0) {
// Work in opposite direction to preserve smaller indexes
for(int i = indexesToRemove.Count - 1; i >= 0; i--) {
entries.RemoveAt(indexesToRemove[i]);
}
OnAclChanged(entriesToRemove.ToArray(), Change.EntryDeleted);
return true;
}
else return false;
}
}
/// <summary>
/// Deletes all the ACL entries for a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <returns><c>true</c> if the entries are deleted, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="subject"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="subject"/> is empty.</exception>
public bool DeleteEntriesForSubject(string subject) {
if(subject == null) throw new ArgumentNullException("subject");
if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject");
lock(this) {
List<int> indexesToRemove = new List<int>(30);
List<AclEntry> entriesToRemove = new List<AclEntry>(30);
for(int i = 0; i < entries.Count; i++) {
if(entries[i].Subject == subject) {
indexesToRemove.Add(i);
entriesToRemove.Add(entries[i]);
}
}
if(indexesToRemove.Count > 0) {
// Work in opposite direction to preserve smaller indexes
for(int i = indexesToRemove.Count - 1; i >= 0; i--) {
entries.RemoveAt(indexesToRemove[i]);
}
OnAclChanged(entriesToRemove.ToArray(), Change.EntryDeleted);
return true;
}
else return false;
}
}
/// <summary>
/// Renames a resource.
/// </summary>
/// <param name="resource">The resource.</param>
/// <param name="newName">The new name of the resource.</param>
/// <returns><c>true</c> if the resource is renamed, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resource"/> or <paramref name="newName"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="resource"/> or <paramref name="newName"/> are empty.</exception>
public bool RenameResource(string resource, string newName) {
if(resource == null) throw new ArgumentNullException("resource");
if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource");
if(newName == null) throw new ArgumentNullException("newName");
if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");
lock(this) {
AclEntry[] entries = RetrieveEntriesForResource(resource);
bool renamed = false;
foreach(AclEntry entry in entries) {
bool deleted = DeleteEntry(entry.Resource, entry.Action, entry.Subject);
if(deleted) {
bool stored = StoreEntry(newName, entry.Action, entry.Subject, entry.Value);
if(stored) renamed = true;
else return false;
}
else return false;
}
return renamed;
}
}
/// <summary>
/// Retrieves all the ACL entries for a resource.
/// </summary>
/// <returns>The entries.</returns>
public AclEntry[] RetrieveAllEntries() {
lock(this) {
return entries.ToArray();
}
}
/// <summary>
/// Retrieves all the ACL entries for a resource.
/// </summary>
/// <param name="resource">The resource.</param>
/// <returns>The entries.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resource"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="resource"/> is empty.</exception>
public AclEntry[] RetrieveEntriesForResource(string resource) {
if(resource == null) throw new ArgumentNullException("resource");
if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource");
lock(this) {
List<AclEntry> result = new List<AclEntry>(10);
foreach(AclEntry e in entries) {
if(e.Resource == resource) result.Add(e);
}
return result.ToArray();
}
}
/// <summary>
/// Retrieves all the ACL entries for a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <returns>The entries.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="subject"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="subject"/> is empty.</exception>
public AclEntry[] RetrieveEntriesForSubject(string subject) {
if(subject == null) throw new ArgumentNullException("subject");
if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject");
lock(this) {
List<AclEntry> result = new List<AclEntry>(10);
foreach(AclEntry e in entries) {
if(e.Subject == subject) result.Add(e);
}
return result.ToArray();
}
}
/// <summary>
/// Initializes the manager data.
/// </summary>
/// <param name="entries">The ACL entries.</param>
/// <exception cref="ArgumentNullException">If <paramref name="entries"/> is <c>null</c>.</exception>
public void InitializeData(AclEntry[] entries) {
if(entries == null) throw new ArgumentNullException("entries");
lock(this) {
this.entries = new List<AclEntry>(entries);
}
}
/// <summary>
/// Gets the total number of ACL entries.
/// </summary>
public int TotalEntries {
get {
lock(this) {
return entries.Count;
}
}
}
/// <summary>
/// Event fired when an ACL entry is stored or deleted.
/// </summary>
public event EventHandler<AclChangedEventArgs> AclChanged;
}
}

107
AclEngine/AclStorerBase.cs Normal file
View file

@ -0,0 +1,107 @@

using System;
using System.Collections.Generic;
using System.Text;
namespace ScrewTurn.Wiki.AclEngine {
/// <summary>
/// Implements a base class for an ACL Storer.
/// </summary>
public abstract class AclStorerBase : IDisposable {
/// <summary>
/// Indicates whether the object was disposed.
/// </summary>
protected bool disposed = false;
/// <summary>
/// The instance of the ACL Manager to handle.
/// </summary>
protected IAclManager aclManager;
/// <summary>
/// The event handler for the <see cref="IAclManager.AclChanged" /> event.
/// </summary>
protected EventHandler<AclChangedEventArgs> aclChangedHandler;
/// <summary>
/// Initializes a new instance of the <see cref="T:AclStorerBase" /> abstract class.
/// </summary>
/// <param name="aclManager">The instance of the ACL Manager to handle.</param>
/// <exception cref="ArgumentNullException">If <paramref name="aclManager"/> is <c>null</c>.</exception>
public AclStorerBase(IAclManager aclManager) {
if(aclManager == null) throw new ArgumentNullException("aclManager");
this.aclManager = aclManager;
aclChangedHandler = new EventHandler<AclChangedEventArgs>(aclManager_AclChanged);
this.aclManager.AclChanged += aclChangedHandler;
}
/// <summary>
/// Handles the <see cref="IAclManager.AclChanged" /> event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The event arguments.</param>
private void aclManager_AclChanged(object sender, AclChangedEventArgs e) {
if(e.Change == Change.EntryDeleted) DeleteEntries(e.Entries);
else if(e.Change == Change.EntryStored) StoreEntries(e.Entries);
else throw new NotSupportedException("Change type not supported");
}
/// <summary>
/// Loads data from storage.
/// </summary>
/// <returns>The loaded ACL entries.</returns>
protected abstract AclEntry[] LoadDataInternal();
/// <summary>
/// Deletes some entries.
/// </summary>
/// <param name="entries">The entries to delete.</param>
protected abstract void DeleteEntries(AclEntry[] entries);
/// <summary>
/// Stores some entries.
/// </summary>
/// <param name="entries">The entries to store.</param>
protected abstract void StoreEntries(AclEntry[] entries);
/// <summary>
/// Loads the data and injects it in the instance of <see cref="T:IAclManager" />.
/// </summary>
public void LoadData() {
lock(this) {
AclEntry[] entries = LoadDataInternal();
aclManager.InitializeData(entries);
}
}
/// <summary>
/// Gets the instance of the ACL Manager.
/// </summary>
public IAclManager AclManager {
get {
lock(this) {
return aclManager;
}
}
}
/// <summary>
/// Disposes the current object.
/// </summary>
public void Dispose() {
lock(this) {
if(!disposed) {
disposed = true;
aclManager.AclChanged -= aclChangedHandler;
}
}
}
}
}

107
AclEngine/IAclManager.cs Normal file
View file

@ -0,0 +1,107 @@

using System;
using System.Collections.Generic;
using System.Text;
namespace ScrewTurn.Wiki.AclEngine {
/// <summary>
/// Defines an interface for an ACL manager.
/// </summary>
public interface IAclManager {
/// <summary>
/// Stores a new ACL entry.
/// </summary>
/// <param name="resource">The controlled resource.</param>
/// <param name="action">The action on the controlled resource.</param>
/// <param name="subject">The subject whose access to the resource/action is controlled.</param>
/// <param name="value">The value of the entry.</param>
/// <returns><c>true</c> if the entry is stored, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resource"/>, <paramref name="action"/> or <paramref name="subject"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="resource"/>, <paramref name="action"/> or <paramref name="subject"/> are empty.</exception>
bool StoreEntry(string resource, string action, string subject, Value value);
/// <summary>
/// Deletes an ACL entry.
/// </summary>
/// <param name="resource">The controlled resource.</param>
/// <param name="action">The action on the controlled resource.</param>
/// <param name="subject">The subject whose access to the resource/action is controlled.</param>
/// <returns><c>true</c> if the entry is deleted, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resource"/>, <paramref name="action"/> or <paramref name="subject"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="resource"/>, <paramref name="action"/> or <paramref name="subject"/> are empty.</exception>
bool DeleteEntry(string resource, string action, string subject);
/// <summary>
/// Deletes all the ACL entries for a resource.
/// </summary>
/// <param name="resource">The controlled resource.</param>
/// <returns><c>true</c> if the entries are deleted, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resource"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="resource"/> is empty.</exception>
bool DeleteEntriesForResource(string resource);
/// <summary>
/// Deletes all the ACL entries for a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <returns><c>true</c> if the entries are deleted, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="subject"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="subject"/> is empty.</exception>
bool DeleteEntriesForSubject(string subject);
/// <summary>
/// Renames a resource.
/// </summary>
/// <param name="resource">The resource.</param>
/// <param name="newName">The new name of the resource.</param>
/// <returns><c>true</c> if the resource is renamed, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resource"/> or <paramref name="newName"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="resource"/> or <paramref name="newName"/> are empty.</exception>
bool RenameResource(string resource, string newName);
/// <summary>
/// Retrieves all the ACL entries for a resource.
/// </summary>
/// <returns>The entries.</returns>
AclEntry[] RetrieveAllEntries();
/// <summary>
/// Retrieves all the ACL entries for a resource.
/// </summary>
/// <param name="resource">The resource.</param>
/// <returns>The entries.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="resource"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="resource"/> is empty.</exception>
AclEntry[] RetrieveEntriesForResource(string resource);
/// <summary>
/// Retrieves all the ACL entries for a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <returns>The entries.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="subject"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="subject"/> is empty.</exception>
AclEntry[] RetrieveEntriesForSubject(string subject);
/// <summary>
/// Initializes the manager data.
/// </summary>
/// <param name="entries">The ACL entries.</param>
/// <exception cref="ArgumentNullException">If <paramref name="entries"/> is <c>null</c>.</exception>
void InitializeData(AclEntry[] entries);
/// <summary>
/// Gets the total number of ACL entries.
/// </summary>
int TotalEntries { get; }
/// <summary>
/// Event fired when an ACL entry is stored or deleted.
/// </summary>
event EventHandler<AclChangedEventArgs> AclChanged;
}
}

View file

@ -0,0 +1,18 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ScrewTurn Wiki ACL Engine")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("3129a8a6-4955-47a3-9a88-3ea42a08b34b")]

20
AssemblyVersion.cs Normal file
View file

@ -0,0 +1,20 @@
using System.Reflection;
[assembly: AssemblyCompany("Dario Solera")]
[assembly: AssemblyProduct("ScrewTurn Wiki")]
[assembly: AssemblyCopyright("Copyright © Dario Solera 2006-2009")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("3.0.0.333")]
[assembly: AssemblyFileVersion("3.0.0.333")]

22
Build - Readme.txt Normal file
View file

@ -0,0 +1,22 @@
ScrewTurn Wiki is written in Microsoft Visual Studio 2008 Professional. The project is structured
as a Visual Studio solution (ScrewTurnWiki.sln).
In order to compile the application you can either build the solution in Visual Studio, or
follow the instructions included in the Build directory.
The CHM documentation file (Help directory) is built using Microsoft Sandcastle [1] and
Sandcastle Help File Builder [2]. The documentation is not necessary to compile or run the application.
Note: if you are using a 64-bit Windows edition, Sandcastle as well as HTML Help Workshop
(distributed with Visual Studio 2008) are installed by default in "C:\Program Files (x86)\".
The SHFB project assumes that the installation directory is "C:\Program Files\".
In order to fix this issue without re-installing the applications or modifying the project file,
you can create a symbolic link using the "mklink" command-line tool (only supported in
Windows Server 2003/2008 and Windows Vista and 7 (you may need to open an elevated command prompt):
mklink /D Sandcastle "C:\Program Files (x86)\Sandcastle"
mklink /D "HTML Help Workshop" "C:\Program Files (x86)\HTML Help Workshop"
[1] http://www.codeplex.com/Sandcastle
[2] http://www.codeplex.com/SHFB

2
Build/Build.bat Normal file
View file

@ -0,0 +1,2 @@
"%WINDIR%\Microsoft.NET\Framework\v3.5\MSBuild.exe" ScrewTurnWiki.msbuild

View file

@ -0,0 +1,2 @@
"%WINDIR%\Microsoft.NET\Framework\v3.5\MSBuild.exe" ScrewTurnWiki.msbuild /t:Package

2
Build/BuildAndTest.bat Normal file
View file

@ -0,0 +1,2 @@
"%WINDIR%\Microsoft.NET\Framework\v3.5\MSBuild.exe" ScrewTurnWiki.msbuild /t:Test /p:Configuration=Debug

View file

@ -0,0 +1,98 @@
Installing or Updating ScrewTurn Wiki
-------------------------------------
This document explains how to install or update ScrewTurn Wiki when using the
SQL Server-based data providers.
INPUT_DIR refers to the WebApplication directory supplied along this file.
TARGET_DIR refers to the directory the application will live in on your server.
This document assumes that you have at least basic knowledge about managing a Windows Server
machine, IIS (Internet Information Services) and Microsoft SQL Server.
If you need help, you can ask questions in the project forum [1].
If you want to host ScrewTurn Wiki using a shared hosting service, or you don't have full
administrative access to your server, please contact either the service's support or the
server administrator.
Please also take a look at other installation packages that are available at the project
website [2], which might better suit your needs.
Clean Installation
------------------
Note: depending on the environment you are running, the following steps might require
minor adjustments.
1. Create TARGET_DIR if it does not already exist; be sure that the directory is empty.
2. Copy all the files from INPUT_DIR into TARGET_DIR.
3. Open the "web.config" file with a text editor and set the MasterPassword field with a password
that will be used for the built-in "admin" account, for example:
<add key="MasterPassword" value="my_password_123" />
4. In the "web.config" file, complete the SQL Server connection string, specifying server address,
database name and authentication information (either user/password or integrated authentication),
for example:
<add key="SettingsStorageProviderConfig" value="Data Source=(local);Initial Catalog=ScrewTurnWiki;User ID=wiki_user;Password=wiki_password_567;" />
5. Make sure that the "web.config" file specifies the correct Settings Storage Provider:
<add key="SettingsStorageProvider" value="ScrewTurn.Wiki.Plugins.SqlServer.SqlServerSettingsStorageProvider, SqlServerProviders.dll" />
6. Create a database in SQL Server with the name specified in the connection string, making sure that
the specified user can create, alter and drop tables as well as select, update, insert and delete rows.
Make sure that the database collation is NOT case sensitive.
7. Setup a Web Site or Application in IIS, setting the root directory to TARGET_DIRECTORY.
8. Navigate to the Web Site or Application using a web browser and verify that everything works properly
(the data providers, if the connection string is correct, will automatically create the required
tables in the database).
Updating From a Previous v3 Release
-----------------------------------
If you upgrading from v2, please refer to our online documentation [3].
Note: depending on the environment you are running, the following steps might require
minor adjustments. If you made modifications to the application, the following steps
might cause issues and make the application unusable and/or unstable.
1. Navigate to TARGET_DIR and verify that the "public" directory only contains the "Plugins" directory.
2. Backup all the content of TARGET_DIR (seriously).
Backup your database (seriously).
3. Rename "app_online.htm" to "app_offline.htm": this will take the application offline.
4. Delete all the content of TARGET_DIR, ***except***:
- "web.config" file
- "app_offline.htm" file.
5. Copy "SqlServerProviders.dll" from the distribution package into "TARGET_DIR\public\Plugins",
replacing the existing file.
6. Select all files in INPUT_DIR, ***except***:
- "web.config" file.
7. Copy all the selected files into TARGET_DIR.
8. Delete "app_offline.htm".
9. Navigate to the wiki using a web browser and verify that everything works properly,
(the data providers, if the connection string is correct, will automatically update the required
tables in the database).
10. Check for plugin updates in the administration interface.
[1] http://www.screwturn.eu/forum
[2] http://www.screwturn.eu
[3] http://www.screwturn.eu/Help.MainPage.ashx

84
Build/Install.txt Normal file
View file

@ -0,0 +1,84 @@
Installing or Updating ScrewTurn Wiki
-------------------------------------
This document explains how to install or update ScrewTurn Wiki when using the built-in
file-based data providers.
INPUT_DIR refers to the WebApplication directory supplied along this file.
TARGET_DIR refers to the directory the application will live in on your server.
This document assumes that you have at least basic knowledge about managing a Windows Server
machine and IIS (Internet Information Services).
If you need help, you can ask questions in the project forum [1].
If you want to host ScrewTurn Wiki using a shared hosting service, or you don't have full
administrative access to your server, please contact either the service's support or the
server administrator.
Please also take a look at other installation packages that are available at the project
website [2], which might better suit your needs.
Clean Installation
------------------
Note: depending on the environment you are running, the following steps might require
minor adjustments.
1. Create TARGET_DIR if it does not already exist; be sure that the directory is empty.
2. Copy all the files from INPUT_DIR into TARGET_DIR.
3. Open the "web.config" file with a text editor and set the MasterPassword field with a password
that will be used for the built-in "admin" account, for example:
<add key="MasterPassword" value="my_password_123" />
4. Set the permissions of the "public" directory so that the ASP.NET Worker Process has "Modify"
permissions on it (usually, the ASP.NET Worker Process runs as "NETWORK SERVICE" or "ASPNET",
depending on the Operating System version).
5. Setup a Web Site or Application in IIS, setting the root directory to TARGET_DIRECTORY.
6. Navigate to the Web Site or Application using a web browser and verify that everything works properly.
Updating From a Previous v3 Release
-----------------------------------
If you upgrading from v2, please refer to our online documentation [3].
Note: depending on the environment you are running, the following steps might require
minor adjustments. If you made modifications to the application, the following steps
might cause issues and make the application unusable and/or unstable.
1. Navigate to TARGET_DIR.
2. Backup all the content of TARGET_DIR (seriously).
3. Rename "app_online.htm" to "app_offline.htm": this will take the application offline.
4. Delete all the content of TARGET_DIR, ***except***:
- "public" directory
- "web.config" file
- "app_offline.htm" file.
5. Select all files in INPUT_DIR, ***except***:
- "public" directory
- "web.config" file.
6. Copy all the selected files into TARGET_DIR.
7. Delete "app_offline.htm".
8. Navigate to the wiki using a web browser and verify that everything works properly.
9. Check for plugin updates in the administration interface.
[1] http://www.screwturn.eu/forum
[2] http://www.screwturn.eu
[3] http://www.screwturn.eu/Help.MainPage.ashx

16
Build/Readme.txt Normal file
View file

@ -0,0 +1,16 @@
In order to build ScrewTurn Wiki, you need Microsoft Visual Studio 2008 (any edition, including Express)
installed on the machine.
ScrewTurnWiki.msbuild contains the build script, written for MSBuild 3.5.
To build the application more easily, use the Build.bad batch file, which compiles the application
and the plugins.
BuildAndTest.bat compiles the application and runs all the unit tests. SQL Server 2005/2008 is required
on the machine and it must allow the necessary access rights to the current Windows user.
BuildAndPackage.bat creates a ZIP archive containing the compiled application and the plugins,
but it requires 7-zip [1] installed on the system.
[1] http://www.7-zip.org

108
Build/ScrewTurnWiki.msbuild Normal file
View file

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Compile" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition="'$(Configuration)' == ''">Release</Configuration>
</PropertyGroup>
<ItemGroup>
<SolutionRoot Include=".." />
<SolutionFile Include="..\ScrewTurnWiki.sln" />
<BuildTemp Include=".\Temp\" />
<Artifacts Include=".\Artifacts\" />
<WebAppOutput Include=".\Artifacts\WebApplication\" />
<WebAppOutput_PublicDirectory Include=".\Artifacts\WebApplication\public\" />
<WebAppOutput_SqlServer Include=".\Artifacts\WebApplication-SqlServer\" />
<WebAppOutput_SqlServer_PublicDirectory Include=".\Artifacts\WebApplication-SqlServer\public\" />
<WebAppOutput_SqlServer_PublicDirectory_Plugins Include=".\Artifacts\WebApplication-SqlServer\public\Plugins\" />
<PluginsOutput Include=".\Artifacts\Plugins\" />
<TestsOutput Include=".\Artifacts\Tests\" />
</ItemGroup>
<Target Name="Clean">
<RemoveDir Directories="@(Artifacts)" />
<RemoveDir Directories="@(Temp)" />
</Target>
<Target Name="Init" DependsOnTargets="Clean">
<MakeDir Directories="@(Artifacts)" />
<MakeDir Directories="@(BuildTemp)" />
<MakeDir Directories="@(WebAppOutput)" />
<MakeDir Directories="@(PluginsOutput)" />
<MakeDir Directories="@(TestsOutput)" />
</Target>
<Target Name="Compile" DependsOnTargets="Init">
<MSBuild Projects="@(SolutionFile)" Properties="OutDir=%(BuildTemp.FullPath);Configuration=$(Configuration)" />
<!-- Web Application -->
<CreateItem Include="Temp\_PublishedWebsites\WebApplication\**\*.*" Exclude="**\*.xml">
<Output ItemName="BuiltWebApp" TaskParameter="Include" />
</CreateItem>
<Copy SourceFiles="@(BuiltWebApp)" DestinationFolder=".\Artifacts\WebApplication\%(RecursiveDir)" />
<!-- Web Application, SQL Server -->
<Copy SourceFiles="@(BuiltWebApp)" DestinationFolder=".\Artifacts\WebApplication-SqlServer\%(RecursiveDir)" />
<!-- Release Web.config -->
<Copy Condition="'$(Configuration)' == 'Release'"
SourceFiles=".\Artifacts\WebApplication\Web.Release.config" DestinationFiles=".\Artifacts\WebApplication\Web.config" />
<Delete Files=".\Artifacts\WebApplication\Web.Release.config" />
<Delete Files=".\Artifacts\WebApplication\Web.SqlServer.Release.config" />
<!-- Release Web.config, SQL Server -->
<Copy Condition="'$(Configuration)' == 'Release'"
SourceFiles=".\Artifacts\WebApplication-SqlServer\Web.SqlServer.Release.config" DestinationFiles=".\Artifacts\WebApplication-SqlServer\Web.config" />
<Delete Files=".\Artifacts\WebApplication-SqlServer\Web.Release.config" />
<Delete Files=".\Artifacts\WebApplication-SqlServer\Web.SqlServer.Release.config" />
<!-- Web Application Public Directory -->
<MakeDir Directories="@(WebAppOutput_PublicDirectory)" />
<!-- Web Application Public Directory and Plugins Directory, SQL Server -->
<MakeDir Directories="@(WebAppOutput_SqlServer_PublicDirectory)" />
<MakeDir Directories="@(WebAppOutput_SqlServer_PublicDirectory_Plugins)" />
<!-- Plugins Assemblies -->
<CreateItem Include="Temp\PluginPack.*;Temp\SqlServerProviders.*">
<Output ItemName="BuiltPlugins" TaskParameter="Include" />
</CreateItem>
<Copy SourceFiles="@(BuiltPlugins)" DestinationFolder=".\Artifacts\Plugins\" />
<!-- SqlServerProviders.dll for SQL Server -->
<Copy SourceFiles=".\Artifacts\Plugins\SqlServerProviders.dll" DestinationFolder=".\Artifacts\WebApplication-SqlServer\public\Plugins\" />
<!-- Install.txt, Install-SqlServer.txt -->
<Copy SourceFiles=".\Install.txt" DestinationFolder=".\Artifacts\" />
<Copy SourceFiles=".\Install-SqlServer.txt" DestinationFolder=".\Artifacts\" />
<!-- Tests Assemblies -->
<CreateItem Include="Temp\*.dll;Temp\*.pdb">
<Output ItemName="BuiltTests" TaskParameter="Include" />
</CreateItem>
<Copy SourceFiles="@(BuiltTests)" DestinationFolder=".\Artifacts\Tests\" />
<!-- Delete Temp directory -->
<RemoveDir Directories="@(BuildTemp)" />
</Target>
<Target Name="Test" DependsOnTargets="Compile">
<!-- Requires SQL Server 2005/2008 installed on the system, and it must grant the necessary rights to the current Windows user -->
<Exec Command='..\References\Tools\NUnit\nunit-console.exe ".\Artifacts\Tests\ScrewTurn.Wiki.AclEngine.Tests.dll" ".\Artifacts\Tests\ScrewTurn.Wiki.Core.Tests.dll" ".\Artifacts\Tests\ScrewTurn.Wiki.Plugins.PluginPack.Tests.dll" ".\Artifacts\Tests\ScrewTurn.Wiki.Plugins.SqlCommon.Tests.dll" ".\Artifacts\Tests\ScrewTurn.Wiki.Plugins.SqlServer.Tests.dll" ".\Artifacts\Tests\ScrewTurn.Wiki.SearchEngine.Tests.dll" /xml=".\Artifacts\TestResult.xml" /labels' />
</Target>
<Target Name="Package" DependsOnTargets="Compile">
<!-- Requires 7-Zip installed on the system in the location %PROGRAMFILES% points to - use a symlink alternatively -->
<GetAssemblyIdentity AssemblyFiles=".\Artifacts\WebApplication\bin\ScrewTurn.Wiki.Core.dll">
<Output TaskParameter="Assemblies" ItemName="MyAsm" />
</GetAssemblyIdentity>
<Exec Command='"$(ProgramFiles)\7-Zip\7z.exe" a -tzip -mx=7 ".\Artifacts\ScrewTurnWiki-%(MyAsm.Version).zip" ".\Artifacts\WebApplication\" ".\Artifacts\Plugins\" ".\Artifacts\Install.txt"' />
<Exec Command='"$(ProgramFiles)\7-Zip\7z.exe" a -tzip -mx=7 ".\Artifacts\ScrewTurnWiki-SqlServer-%(MyAsm.Version).zip" ".\Artifacts\WebApplication-SqlServer\" ".\Artifacts\Plugins\" ".\Artifacts\Install-SqlServer.txt"' />
</Target>
</Project>

View file

@ -0,0 +1,148 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using NUnit.Framework;
using Rhino.Mocks;
using ScrewTurn.Wiki.AclEngine;
namespace ScrewTurn.Wiki.Tests {
[TestFixture]
public class AclStorerTests {
private string testFile = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "__ACL_File.dat");
private MockRepository mocks = new MockRepository();
private IAclManager MockAclManager() {
IAclManager manager = mocks.DynamicMock<AclManagerBase>();
return manager;
}
[TearDown]
public void TearDown() {
try {
File.Delete(testFile);
}
catch { }
}
[Test]
public void Constructor() {
IAclManager manager = MockAclManager();
AclStorer storer = new AclStorer(manager, testFile);
Assert.AreSame(manager, storer.AclManager, "Wrong ACL Manager instance");
}
[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void Constructor_NullAclManager() {
AclStorer storer = new AclStorer(null, testFile);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void Constructor_InvalidFile(string f) {
AclStorer storer = new AclStorer(MockAclManager(), f);
}
private void AssertAclEntriesAreEqual(AclEntry expected, AclEntry actual) {
Assert.AreEqual(expected.Resource, actual.Resource, "Wrong resource");
Assert.AreEqual(expected.Action, actual.Action, "Wrong action");
Assert.AreEqual(expected.Subject, actual.Subject, "Wrong subject");
Assert.AreEqual(expected.Value, actual.Value, "Wrong value");
}
[Test]
public void Store_LoadData() {
IAclManager manager = MockAclManager();
AclStorer storer = new AclStorer(manager, testFile);
manager.StoreEntry("Res1", "Action1", "U.User", Value.Grant);
manager.StoreEntry("Res2", "Action2", "G.Group", Value.Deny);
storer.Dispose();
storer = null;
manager = MockAclManager();
storer = new AclStorer(manager, testFile);
storer.LoadData();
Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count");
AclEntry[] allEntries = manager.RetrieveAllEntries();
Assert.AreEqual(2, allEntries.Length, "Wrong entry count");
Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); });
AssertAclEntriesAreEqual(new AclEntry("Res2", "Action2", "G.Group", Value.Deny), allEntries[0]);
AssertAclEntriesAreEqual(new AclEntry("Res1", "Action1", "U.User", Value.Grant), allEntries[1]);
}
[Test]
public void Delete_LoadData() {
IAclManager manager = MockAclManager();
AclStorer storer = new AclStorer(manager, testFile);
manager.StoreEntry("Res1", "Action1", "U.User", Value.Grant);
manager.StoreEntry("Res2", "Action2", "G.Group", Value.Deny);
manager.StoreEntry("Res3", "Action1", "U.User", Value.Grant);
manager.StoreEntry("Res3", "Action2", "G.Group", Value.Deny);
manager.DeleteEntriesForResource("Res3");
storer.Dispose();
storer = null;
manager = MockAclManager();
storer = new AclStorer(manager, testFile);
storer.LoadData();
Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count");
AclEntry[] allEntries = manager.RetrieveAllEntries();
Assert.AreEqual(2, allEntries.Length, "Wrong entry count");
Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); });
AssertAclEntriesAreEqual(new AclEntry("Res2", "Action2", "G.Group", Value.Deny), allEntries[0]);
AssertAclEntriesAreEqual(new AclEntry("Res1", "Action1", "U.User", Value.Grant), allEntries[1]);
}
[Test]
public void Overwrite_LoadData() {
IAclManager manager = MockAclManager();
AclStorer storer = new AclStorer(manager, testFile);
manager.StoreEntry("Res1", "Action1", "U.User", Value.Grant);
manager.StoreEntry("Res2", "Action2", "G.Group", Value.Grant);
manager.StoreEntry("Res2", "Action2", "G.Group", Value.Deny); // Overwrite
storer.Dispose();
storer = null;
manager = MockAclManager();
storer = new AclStorer(manager, testFile);
storer.LoadData();
Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count");
AclEntry[] allEntries = manager.RetrieveAllEntries();
Assert.AreEqual(2, allEntries.Length, "Wrong entry count");
Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); });
AssertAclEntriesAreEqual(new AclEntry("Res2", "Action2", "G.Group", Value.Deny), allEntries[0]);
AssertAclEntriesAreEqual(new AclEntry("Res1", "Action1", "U.User", Value.Grant), allEntries[1]);
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,66 @@

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
namespace ScrewTurn.Wiki.Tests {
[TestFixture]
public class AuthToolsTests {
[Test]
public void Static_PrepareUsername() {
Assert.AreEqual("U.User", AuthTools.PrepareUsername("User"), "Wrong result");
Assert.AreEqual("U.U.User", AuthTools.PrepareUsername("U.User"), "Wrong result");
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void Static_PrepareUsername_InvalidUsername(string s) {
AuthTools.PrepareUsername(s);
}
[Test]
public void Static_PrepareGroups() {
Assert.AreEqual(0, AuthTools.PrepareGroups(new string[0]).Length, "Wrong result length");
string[] input = new string[] { "Group", "G.Group" };
string[] output = AuthTools.PrepareGroups(input);
Assert.AreEqual(input.Length, output.Length, "Wrong result length");
for(int i = 0; i < input.Length; i++) {
Assert.AreEqual("G." + input[i], output[i], "Wrong value");
}
}
[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void Static_PrepareGroups_NullGroups() {
AuthTools.PrepareGroups(null);
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void Static_PrepareGroups_InvalidElement(string e) {
AuthTools.PrepareGroups(new string[] { e });
}
[TestCase(null, false, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", false, ExpectedException = typeof(ArgumentException))]
[TestCase("G", false, ExpectedException = typeof(ArgumentException))]
[TestCase("G.", true)]
[TestCase("g.", true)]
[TestCase("G.Blah", true)]
[TestCase("g.Blah", true)]
[TestCase("U.", false)]
[TestCase("u.", false)]
[TestCase("U.Blah", false)]
[TestCase("u.Blah", false)]
public void Static_IsGroup(string subject, bool result) {
Assert.AreEqual(result, AuthTools.IsGroup(subject), "Wrong result");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using Rhino.Mocks;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki.Tests {
public class CacheProviderTests : CacheProviderTestScaffolding {
public override ICacheProviderV30 GetProvider() {
CacheProvider prov = new CacheProvider();
prov.Init(MockHost(), "");
return prov;
}
}
}

View file

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{013B5DA5-76F9-4D7F-A174-4926BF51E24B}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ScrewTurn.Wiki.Tests</RootNamespace>
<AssemblyName>ScrewTurn.Wiki.Core.Tests</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Reference Include="nunit.framework, Version=2.5.1.9189, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\References\Tools\NUnit\framework\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="Rhino.Mocks, Version=3.5.0.1337, Culture=neutral, PublicKeyToken=0b3305902db7183f, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\References\Tools\Rhino.Mocks\Rhino.Mocks.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Web" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\AssemblyVersion.cs">
<Link>AssemblyVersion.cs</Link>
</Compile>
<Compile Include="..\SearchEngine-Tests\TestsBase.cs">
<Link>TestsBase.cs</Link>
</Compile>
<Compile Include="AclStorerTests.cs" />
<Compile Include="AuthCheckerTests.cs" />
<Compile Include="AuthReaderTests.cs" />
<Compile Include="AuthToolsTests.cs" />
<Compile Include="AuthWriterTests.cs" />
<Compile Include="CacheProviderTests.cs" />
<Compile Include="DataMigratorTests.cs" />
<Compile Include="FilesStorageProviderTests.cs" />
<Compile Include="FormatterTests.cs" />
<Compile Include="IndexStorerTests.cs" />
<Compile Include="PagesStorageProviderTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProviderLoaderTests.cs" />
<Compile Include="SettingsStorageProviderTests.cs" />
<Compile Include="TestSettingsStorageProvider.cs" />
<Compile Include="UsersStorageProviderTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AclEngine\AclEngine.csproj">
<Project>{44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}</Project>
<Name>AclEngine</Name>
</ProjectReference>
<ProjectReference Include="..\Core\Core.csproj">
<Project>{C353A35C-86D0-4154-9500-4F88CAAB29C3}</Project>
<Name>Core</Name>
</ProjectReference>
<ProjectReference Include="..\PluginFramework\PluginFramework.csproj">
<Project>{531A83D6-76F9-4014-91C5-295818E2D948}</Project>
<Name>PluginFramework</Name>
</ProjectReference>
<ProjectReference Include="..\SearchEngine\SearchEngine.csproj">
<Project>{2DF980A6-4742-49B1-A090-DE79314644D0}</Project>
<Name>SearchEngine</Name>
</ProjectReference>
<ProjectReference Include="..\TestScaffolding\TestScaffolding.csproj">
<Project>{F865670A-DEDE-41B5-B426-48D73C3B5B1C}</Project>
<Name>TestScaffolding</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -0,0 +1,647 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using NUnit.Framework;
using Rhino.Mocks;
using RMC = Rhino.Mocks.Constraints;
using ScrewTurn.Wiki.PluginFramework;
using ScrewTurn.Wiki.AclEngine;
namespace ScrewTurn.Wiki.Tests {
[TestFixture]
public class DataMigratorTests {
[Test]
public void MigratePagesStorageProviderData() {
MockRepository mocks = new MockRepository();
IPagesStorageProviderV30 source = mocks.StrictMock<IPagesStorageProviderV30>();
IPagesStorageProviderV30 destination = mocks.StrictMock<IPagesStorageProviderV30>();
// Setup SOURCE -------------------------
// Setup snippets
Snippet s1 = new Snippet("S1", "Blah1", source);
Snippet s2 = new Snippet("S2", "Blah2", source);
Expect.Call(source.GetSnippets()).Return(new Snippet[] { s1, s2 });
// Setup content templates
ContentTemplate ct1 = new ContentTemplate("CT1", "Template 1", source);
ContentTemplate ct2 = new ContentTemplate("CT2", "Template 2", source);
Expect.Call(source.GetContentTemplates()).Return(new ContentTemplate[] { ct1, ct2 });
// Setup namespaces
NamespaceInfo ns1 = new NamespaceInfo("NS1", source, null);
NamespaceInfo ns2 = new NamespaceInfo("NS2", source, null);
Expect.Call(source.GetNamespaces()).Return(new NamespaceInfo[] { ns1, ns2 });
// Setup pages
PageInfo p1 = new PageInfo("Page", source, DateTime.Now);
PageInfo p2 = new PageInfo(NameTools.GetFullName(ns1.Name, "Page"), source, DateTime.Now);
PageInfo p3 = new PageInfo(NameTools.GetFullName(ns1.Name, "Page1"), source, DateTime.Now);
Expect.Call(source.GetPages(null)).Return(new PageInfo[] { p1 });
Expect.Call(source.GetPages(ns1)).Return(new PageInfo[] { p2, p3 });
Expect.Call(source.GetPages(ns2)).Return(new PageInfo[0]);
// Set default page for NS1
ns1.DefaultPage = p2;
// Setup categories/bindings
CategoryInfo c1 = new CategoryInfo("Cat", source);
c1.Pages = new string[] { p1.FullName };
CategoryInfo c2 = new CategoryInfo(NameTools.GetFullName(ns1.Name, "Cat"), source);
c2.Pages = new string[] { p2.FullName };
CategoryInfo c3 = new CategoryInfo(NameTools.GetFullName(ns1.Name, "Cat1"), source);
c3.Pages = new string[0];
Expect.Call(source.GetCategories(null)).Return(new CategoryInfo[] { c1 });
Expect.Call(source.GetCategories(ns1)).Return(new CategoryInfo[] { c2, c3 });
Expect.Call(source.GetCategories(ns2)).Return(new CategoryInfo[0]);
// Setup drafts
PageContent d1 = new PageContent(p1, "Draft", "NUnit", DateTime.Now, "Comm", "Cont", new string[] { "k1", "k2" }, "Descr");
Expect.Call(source.GetDraft(p1)).Return(d1);
Expect.Call(source.GetDraft(p2)).Return(null);
Expect.Call(source.GetDraft(p3)).Return(null);
// Setup content
PageContent ctn1 = new PageContent(p1, "Title1", "User1", DateTime.Now, "Comm1", "Cont1", null, "Descr1");
PageContent ctn2 = new PageContent(p2, "Title2", "User2", DateTime.Now, "Comm2", "Cont2", null, "Descr2");
PageContent ctn3 = new PageContent(p3, "Title3", "User3", DateTime.Now, "Comm3", "Cont3", null, "Descr3");
Expect.Call(source.GetContent(p1)).Return(ctn1);
Expect.Call(source.GetContent(p2)).Return(ctn2);
Expect.Call(source.GetContent(p3)).Return(ctn3);
// Setup backups
Expect.Call(source.GetBackups(p1)).Return(new int[] { 0, 1 });
Expect.Call(source.GetBackups(p2)).Return(new int[] { 0 });
Expect.Call(source.GetBackups(p3)).Return(new int[0]);
PageContent bak1_0 = new PageContent(p1, "K1_0", "U1_0", DateTime.Now, "", "Cont", null, null);
PageContent bak1_1 = new PageContent(p1, "K1_1", "U1_1", DateTime.Now, "", "Cont", null, null);
PageContent bak2_0 = new PageContent(p2, "K2_0", "U2_0", DateTime.Now, "", "Cont", null, null);
Expect.Call(source.GetBackupContent(p1, 0)).Return(bak1_0);
Expect.Call(source.GetBackupContent(p1, 1)).Return(bak1_1);
Expect.Call(source.GetBackupContent(p2, 0)).Return(bak2_0);
// Messages
Message m1 = new Message(1, "User1", "Subject1", DateTime.Now, "Body1");
m1.Replies = new Message[] { new Message(2, "User2", "Subject2", DateTime.Now, "Body2") };
Message[] p1m = new Message[] { m1 };
Message[] p2m = new Message[0];
Message[] p3m = new Message[0];
Expect.Call(source.GetMessages(p1)).Return(p1m);
Expect.Call(source.GetMessages(p2)).Return(p2m);
Expect.Call(source.GetMessages(p3)).Return(p3m);
// Setup navigation paths
NavigationPath n1 = new NavigationPath("N1", source);
n1.Pages = new string[] { p1.FullName };
NavigationPath n2 = new NavigationPath(NameTools.GetFullName(ns1.Name, "N1"), source);
n2.Pages = new string[] { p2.FullName, p3.FullName };
Expect.Call(source.GetNavigationPaths(null)).Return(new NavigationPath[] { n1 });
Expect.Call(source.GetNavigationPaths(ns1)).Return(new NavigationPath[] { n2 });
Expect.Call(source.GetNavigationPaths(ns2)).Return(new NavigationPath[0]);
// Setup DESTINATION --------------------------
// Snippets
Expect.Call(destination.AddSnippet(s1.Name, s1.Content)).Return(new Snippet(s1.Name, s1.Content, destination));
Expect.Call(source.RemoveSnippet(s1.Name)).Return(true);
Expect.Call(destination.AddSnippet(s2.Name, s2.Content)).Return(new Snippet(s2.Name, s2.Content, destination));
Expect.Call(source.RemoveSnippet(s2.Name)).Return(true);
// Content templates
Expect.Call(destination.AddContentTemplate(ct1.Name, ct1.Content)).Return(new ContentTemplate(ct1.Name, ct1.Name, destination));
Expect.Call(source.RemoveContentTemplate(ct1.Name)).Return(true);
Expect.Call(destination.AddContentTemplate(ct2.Name, ct2.Content)).Return(new ContentTemplate(ct2.Name, ct2.Name, destination));
Expect.Call(source.RemoveContentTemplate(ct2.Name)).Return(true);
// Namespaces
NamespaceInfo ns1Out = new NamespaceInfo(ns1.Name, destination, null);
NamespaceInfo ns2Out = new NamespaceInfo(ns2.Name, destination, null);
Expect.Call(destination.AddNamespace(ns1.Name)).Return(ns1Out);
Expect.Call(source.RemoveNamespace(ns1)).Return(true);
Expect.Call(destination.AddNamespace(ns2.Name)).Return(ns2Out);
Expect.Call(source.RemoveNamespace(ns2)).Return(true);
// Pages/drafts/content/backups/messages
PageInfo p1Out = new PageInfo(p1.FullName, destination, p1.CreationDateTime);
Expect.Call(destination.AddPage(null, p1.FullName, p1.CreationDateTime)).Return(p1Out);
Expect.Call(destination.ModifyPage(p1Out, ctn1.Title, ctn1.User, ctn1.LastModified, ctn1.Comment, ctn1.Content, ctn1.Keywords, ctn1.Description, SaveMode.Normal)).Return(true);
Expect.Call(destination.ModifyPage(p1Out, d1.Title, d1.User, d1.LastModified, d1.Comment, d1.Content, d1.Keywords, d1.Description, SaveMode.Draft)).Return(true);
Expect.Call(destination.SetBackupContent(bak1_0, 0)).Return(true);
Expect.Call(destination.SetBackupContent(bak1_1, 1)).Return(true);
Expect.Call(destination.BulkStoreMessages(p1Out, p1m)).Return(true);
Expect.Call(source.RemovePage(p1)).Return(true);
PageInfo p2Out = new PageInfo(p2.FullName, destination, p2.CreationDateTime);
Expect.Call(destination.AddPage(NameTools.GetNamespace(p2.FullName), NameTools.GetLocalName(p2.FullName),
p2.CreationDateTime)).Return(p2Out);
Expect.Call(destination.ModifyPage(p2Out, ctn2.Title, ctn2.User, ctn2.LastModified, ctn2.Comment, ctn2.Content, ctn2.Keywords, ctn2.Description, SaveMode.Normal)).Return(true);
Expect.Call(destination.SetBackupContent(bak2_0, 0)).Return(true);
Expect.Call(destination.BulkStoreMessages(p2Out, p2m)).Return(true);
Expect.Call(source.RemovePage(p2)).Return(true);
PageInfo p3Out = new PageInfo(p3.FullName, destination, p3.CreationDateTime);
Expect.Call(destination.AddPage(NameTools.GetNamespace(p3.FullName), NameTools.GetLocalName(p3.FullName),
p3.CreationDateTime)).Return(p3Out);
Expect.Call(destination.ModifyPage(p3Out, ctn3.Title, ctn3.User, ctn3.LastModified, ctn3.Comment, ctn3.Content, ctn3.Keywords, ctn3.Description, SaveMode.Normal)).Return(true);
Expect.Call(destination.BulkStoreMessages(p3Out, p3m)).Return(true);
Expect.Call(source.RemovePage(p3)).Return(true);
// Categories/bindings
CategoryInfo c1Out = new CategoryInfo(c1.FullName, destination);
CategoryInfo c2Out = new CategoryInfo(c2.FullName, destination);
CategoryInfo c3Out = new CategoryInfo(c3.FullName, destination);
Expect.Call(destination.AddCategory(null, c1.FullName)).Return(c1Out);
Expect.Call(destination.AddCategory(NameTools.GetNamespace(c2.FullName), NameTools.GetLocalName(c2.FullName))).Return(c2Out);
Expect.Call(destination.AddCategory(NameTools.GetNamespace(c3.FullName), NameTools.GetLocalName(c3.FullName))).Return(c3Out);
Expect.Call(destination.RebindPage(p1Out, new string[] { c1.FullName })).Return(true);
Expect.Call(destination.RebindPage(p2Out, new string[] { c2.FullName })).Return(true);
Expect.Call(destination.RebindPage(p3Out, new string[0])).Return(true);
Expect.Call(source.RemoveCategory(c1)).Return(true);
Expect.Call(source.RemoveCategory(c2)).Return(true);
Expect.Call(source.RemoveCategory(c3)).Return(true);
// Navigation paths
NavigationPath n1Out = new NavigationPath(n1.FullName, destination);
n1Out.Pages = n1.Pages;
NavigationPath n2Out = new NavigationPath(n2.FullName, destination);
n2Out.Pages = n2.Pages;
Expect.Call(destination.AddNavigationPath(null, n1.FullName, new PageInfo[] { p1 })).Return(n1Out).Constraints(
RMC.Is.Null(), RMC.Is.Equal(n1.FullName),
RMC.Is.Matching<PageInfo[]>(delegate(PageInfo[] array) {
return array[0].FullName == p1.FullName;
}));
Expect.Call(destination.AddNavigationPath(NameTools.GetNamespace(n2.FullName), NameTools.GetLocalName(n2.FullName), new PageInfo[] { p2, p3 })).Return(n2Out).Constraints(
RMC.Is.Equal(NameTools.GetNamespace(n2.FullName)), RMC.Is.Equal(NameTools.GetLocalName(n2.FullName)),
RMC.Is.Matching<PageInfo[]>(delegate(PageInfo[] array) {
return array[0].FullName == p2.FullName && array[1].FullName == p3.FullName;
}));
Expect.Call(source.RemoveNavigationPath(n1)).Return(true);
Expect.Call(source.RemoveNavigationPath(n2)).Return(true);
Expect.Call(destination.SetNamespaceDefaultPage(ns1Out, p2Out)).Return(ns1Out);
Expect.Call(destination.SetNamespaceDefaultPage(ns2Out, null)).Return(ns2Out);
// Used for navigation paths
Expect.Call(destination.GetPages(null)).Return(new PageInfo[] { p1Out });
Expect.Call(destination.GetPages(ns1Out)).Return(new PageInfo[] { p2Out, p3Out });
Expect.Call(destination.GetPages(ns2Out)).Return(new PageInfo[0]);
mocks.Replay(source);
mocks.Replay(destination);
DataMigrator.MigratePagesStorageProviderData(source, destination);
mocks.Verify(source);
mocks.Verify(destination);
}
[Test]
public void MigrateUsersStorageProviderData() {
MockRepository mocks = new MockRepository();
IUsersStorageProviderV30 source = mocks.StrictMock<IUsersStorageProviderV30>();
IUsersStorageProviderV30 destination = mocks.StrictMock<IUsersStorageProviderV30>();
// Setup SOURCE --------------------
// User groups
UserGroup g1 = new UserGroup("G1", "G1", source);
UserGroup g2 = new UserGroup("G2", "G2", source);
Expect.Call(source.GetUserGroups()).Return(new UserGroup[] { g1, g2 });
// Users
UserInfo u1 = new UserInfo("U1", "U1", "u1@users.com", true, DateTime.Now, source);
UserInfo u2 = new UserInfo("U2", "U2", "u2@users.com", true, DateTime.Now, source);
UserInfo u3 = new UserInfo("U3", "U3", "u3@users.com", true, DateTime.Now, source);
Expect.Call(source.GetUsers()).Return(new UserInfo[] { u1, u2, u3 });
// Membership
g1.Users = new string[] { u1.Username, u2.Username };
g2.Users = new string[] { u2.Username, u3.Username };
u1.Groups = new string[] { g1.Name };
u2.Groups = new string[] { g1.Name, g2.Name };
u3.Groups = new string[] { g2.Name };
// User data
IDictionary<string, string> u1Data = new Dictionary<string, string>() {
{ "Key1", "Value1" },
{ "Key2", "Value2" }
};
Expect.Call(source.RetrieveAllUserData(u1)).Return(u1Data);
Expect.Call(source.RetrieveAllUserData(u2)).Return(new Dictionary<string, string>());
Expect.Call(source.RetrieveAllUserData(u3)).Return(new Dictionary<string, string>());
// Setup DESTINATION ------------------
// User groups
UserGroup g1Out = new UserGroup(g1.Name, g1.Description, destination);
UserGroup g2Out = new UserGroup(g2.Name, g2.Description, destination);
Expect.Call(destination.AddUserGroup(g1.Name, g1.Description)).Return(g1Out);
Expect.Call(destination.AddUserGroup(g2.Name, g2.Description)).Return(g2Out);
// Users
UserInfo u1Out = new UserInfo(u1.Username, u1.DisplayName, u1.Email, u1.Active, u1.DateTime, destination);
UserInfo u2Out = new UserInfo(u2.Username, u2.DisplayName, u2.Email, u2.Active, u2.DateTime, destination);
UserInfo u3Out = new UserInfo(u3.Username, u3.DisplayName, u3.Email, u3.Active, u3.DateTime, destination);
Expect.Call(destination.AddUser(u1.Username, u1.DisplayName, null, u1.Email, u1.Active, u1.DateTime)).Return(u1Out).Constraints(
RMC.Is.Equal(u1.Username), RMC.Is.Equal(u1.DisplayName), RMC.Is.Anything(), RMC.Is.Equal(u1.Email), RMC.Is.Equal(u1.Active), RMC.Is.Equal(u1.DateTime));
Expect.Call(destination.AddUser(u2.Username, u2.DisplayName, null, u2.Email, u2.Active, u2.DateTime)).Return(u2Out).Constraints(
RMC.Is.Equal(u2.Username), RMC.Is.Equal(u2.DisplayName), RMC.Is.Anything(), RMC.Is.Equal(u2.Email), RMC.Is.Equal(u2.Active), RMC.Is.Equal(u2.DateTime));
Expect.Call(destination.AddUser(u3.Username, u3.DisplayName, null, u3.Email, u3.Active, u3.DateTime)).Return(u3Out).Constraints(
RMC.Is.Equal(u3.Username), RMC.Is.Equal(u3.DisplayName), RMC.Is.Anything(), RMC.Is.Equal(u3.Email), RMC.Is.Equal(u3.Active), RMC.Is.Equal(u3.DateTime));
// Membership
Expect.Call(destination.SetUserMembership(u1Out, u1.Groups)).Return(u1Out);
Expect.Call(destination.SetUserMembership(u2Out, u2.Groups)).Return(u2Out);
Expect.Call(destination.SetUserMembership(u3Out, u3.Groups)).Return(u3Out);
// User data
Expect.Call(destination.StoreUserData(u1Out, "Key1", "Value1")).Return(true);
Expect.Call(destination.StoreUserData(u1Out, "Key2", "Value2")).Return(true);
// Delete source data
Expect.Call(source.RemoveUser(u1)).Return(true);
Expect.Call(source.RemoveUser(u2)).Return(true);
Expect.Call(source.RemoveUser(u3)).Return(true);
Expect.Call(source.RemoveUserGroup(g1)).Return(true);
Expect.Call(source.RemoveUserGroup(g2)).Return(true);
mocks.Replay(source);
mocks.Replay(destination);
DataMigrator.MigrateUsersStorageProviderData(source, destination, false);
mocks.Verify(source);
mocks.Verify(destination);
}
private delegate bool RetrieveFile(string file, Stream stream, bool count);
private delegate bool RetrieveAttachment(PageInfo page, string file, Stream stream, bool count);
private delegate bool StoreFile(string file, Stream stream, bool overwrite);
private delegate bool StoreAttachment(PageInfo page, string file, Stream stream, bool overwrite);
[Test]
public void MigrateFilesStorageProviderData() {
MockRepository mocks = new MockRepository();
IFilesStorageProviderV30 source = mocks.StrictMock<IFilesStorageProviderV30>();
IFilesStorageProviderV30 destination = mocks.StrictMock<IFilesStorageProviderV30>();
ISettingsStorageProviderV30 settingsProvider = mocks.StrictMock<ISettingsStorageProviderV30>();
IAclManager aclManager = mocks.StrictMock<IAclManager>();
Expect.Call(settingsProvider.AclManager).Return(aclManager).Repeat.Any();
// Setup SOURCE -----------------
// Directories
Expect.Call(source.ListDirectories("/")).Return(new string[] { "/Dir1/", "/Dir2/" });
Expect.Call(source.ListDirectories("/Dir1/")).Return(new string[] { "/Dir1/Sub/" });
Expect.Call(source.ListDirectories("/Dir2/")).Return(new string[0]);
Expect.Call(source.ListDirectories("/Dir1/Sub/")).Return(new string[0]);
// Settings (permissions)
Expect.Call(aclManager.RenameResource(
Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(source, "/"),
Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(destination, "/"))).Return(true);
Expect.Call(aclManager.RenameResource(
Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(source, "/Dir1/"),
Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(destination, "/Dir1/"))).Return(true);
Expect.Call(aclManager.RenameResource(
Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(source, "/Dir2/"),
Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(destination, "/Dir2/"))).Return(true);
Expect.Call(aclManager.RenameResource(
Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(source, "/Dir1/Sub/"),
Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(destination, "/Dir1/Sub/"))).Return(true);
// Filenames
Expect.Call(source.ListFiles("/")).Return(new string[] { "/File1.txt", "/File2.txt" });
Expect.Call(source.ListFiles("/Dir1/")).Return(new string[] { "/Dir1/File.txt" });
Expect.Call(source.ListFiles("/Dir2/")).Return(new string[0]);
Expect.Call(source.ListFiles("/Dir1/Sub/")).Return(new string[] { "/Dir1/Sub/File.txt" });
// File content
Expect.Call(source.RetrieveFile("/File1.txt", null, false)).Constraints(
RMC.Is.Equal("/File1.txt"), RMC.Is.TypeOf<Stream>(), RMC.Is.Equal(false)).Do(
new RetrieveFile(
delegate(string file, Stream stream, bool count) {
byte[] stuff = Encoding.Unicode.GetBytes("content1");
stream.Write(stuff, 0, stuff.Length);
return true;
}));
Expect.Call(source.RetrieveFile("/File2.txt", null, false)).Constraints(
RMC.Is.Equal("/File2.txt"), RMC.Is.TypeOf<Stream>(), RMC.Is.Equal(false)).Do(
new RetrieveFile(
delegate(string file, Stream stream, bool count) {
byte[] stuff = Encoding.Unicode.GetBytes("content2");
stream.Write(stuff, 0, stuff.Length);
return true;
}));
Expect.Call(source.RetrieveFile("/Dir1/File.txt", null, false)).Constraints(
RMC.Is.Equal("/Dir1/File.txt"), RMC.Is.TypeOf<Stream>(), RMC.Is.Equal(false)).Do(
new RetrieveFile(
delegate(string file, Stream stream, bool count) {
byte[] stuff = Encoding.Unicode.GetBytes("content3");
stream.Write(stuff, 0, stuff.Length);
return true;
}));
Expect.Call(source.RetrieveFile("/Dir1/Sub/File.txt", null, false)).Constraints(
RMC.Is.Equal("/Dir1/Sub/File.txt"), RMC.Is.TypeOf<Stream>(), RMC.Is.Equal(false)).Do(
new RetrieveFile(
delegate(string file, Stream stream, bool count) {
byte[] stuff = Encoding.Unicode.GetBytes("content4");
stream.Write(stuff, 0, stuff.Length);
return true;
}));
// File details
Expect.Call(source.GetFileDetails("/File1.txt")).Return(new FileDetails(8, DateTime.Now, 52));
Expect.Call(source.GetFileDetails("/File2.txt")).Return(new FileDetails(8, DateTime.Now, 0));
Expect.Call(source.GetFileDetails("/Dir1/File.txt")).Return(new FileDetails(8, DateTime.Now, 21));
Expect.Call(source.GetFileDetails("/Dir1/Sub/File.txt")).Return(new FileDetails(8, DateTime.Now, 123));
// Page attachments
Expect.Call(source.GetPagesWithAttachments()).Return(new string[] { "MainPage", "Sub.Page", "Sub.Another" });
Expect.Call(source.ListPageAttachments(null)).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "MainPage"; })).Return(new string[] { "Attachment.txt" });
Expect.Call(source.ListPageAttachments(null)).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Page"; })).Return(new string[] { "Attachment2.txt" });
Expect.Call(source.ListPageAttachments(null)).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Another"; })).Return(new string[0]);
// Page attachment content
Expect.Call(source.RetrievePageAttachment(null, "Attachment.txt", null, false)).Constraints(
RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "MainPage"; }), RMC.Is.Equal("Attachment.txt"), RMC.Is.TypeOf<Stream>(), RMC.Is.Equal(false)).Do(
new RetrieveAttachment(
delegate(PageInfo page, string name, Stream stream, bool count) {
byte[] stuff = Encoding.Unicode.GetBytes("content5");
stream.Write(stuff, 0, stuff.Length);
return true;
}));
Expect.Call(source.RetrievePageAttachment(null, "Attachment2.txt", null, false)).Constraints(
RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Page"; }), RMC.Is.Equal("Attachment2.txt"), RMC.Is.TypeOf<Stream>(), RMC.Is.Equal(false)).Do(
new RetrieveAttachment(
delegate(PageInfo page, string name, Stream stream, bool count) {
byte[] stuff = Encoding.Unicode.GetBytes("content6");
stream.Write(stuff, 0, stuff.Length);
return true;
}));
// Attachment details
Expect.Call(source.GetPageAttachmentDetails(null, "Attachment.txt")).Constraints(
RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "MainPage"; }), RMC.Is.Equal("Attachment.txt")).Return(new FileDetails(8, DateTime.Now, 8));
Expect.Call(source.GetPageAttachmentDetails(null, "Attachment2.txt")).Constraints(
RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Page"; }), RMC.Is.Equal("Attachment2.txt")).Return(new FileDetails(8, DateTime.Now, 29));
// Setup DESTINATION ------------------------
// Directories
Expect.Call(destination.CreateDirectory("/", "Dir1")).Return(true);
Expect.Call(destination.CreateDirectory("/", "Dir2")).Return(true);
Expect.Call(destination.CreateDirectory("/Dir1/", "Sub")).Return(true);
// Files
Expect.Call(destination.StoreFile("/File1.txt", null, false)).Constraints(
RMC.Is.Equal("/File1.txt"), RMC.Is.TypeOf<Stream>(), RMC.Is.Equal(false)).Do(new StoreFile(
delegate(string name, Stream stream, bool overwrite) {
byte[] buff = new byte[512];
int read = stream.Read(buff, 0, (int)stream.Length);
Assert.AreEqual("content1", Encoding.Unicode.GetString(buff, 0, read), "Wrong data");
return true;
}));
Expect.Call(destination.StoreFile("/File2.txt", null, false)).Constraints(
RMC.Is.Equal("/File2.txt"), RMC.Is.TypeOf<Stream>(), RMC.Is.Equal(false)).Do(new StoreFile(
delegate(string name, Stream stream, bool overwrite) {
byte[] buff = new byte[512];
int read = stream.Read(buff, 0, (int)stream.Length);
Assert.AreEqual("content2", Encoding.Unicode.GetString(buff, 0, read), "Wrong data");
return true;
}));
Expect.Call(destination.StoreFile("/Dir1/File.txt", null, false)).Constraints(
RMC.Is.Equal("/Dir1/File.txt"), RMC.Is.TypeOf<Stream>(), RMC.Is.Equal(false)).Do(new StoreFile(
delegate(string name, Stream stream, bool overwrite) {
byte[] buff = new byte[512];
int read = stream.Read(buff, 0, (int)stream.Length);
Assert.AreEqual("content3", Encoding.Unicode.GetString(buff, 0, read), "Wrong data");
return true;
}));
Expect.Call(destination.StoreFile("/Dir1/Sub/File.txt", null, false)).Constraints(
RMC.Is.Equal("/Dir1/Sub/File.txt"), RMC.Is.TypeOf<Stream>(), RMC.Is.Equal(false)).Do(new StoreFile(
delegate(string name, Stream stream, bool overwrite) {
byte[] buff = new byte[512];
int read = stream.Read(buff, 0, (int)stream.Length);
Assert.AreEqual("content4", Encoding.Unicode.GetString(buff, 0, read), "Wrong data");
return true;
}));
// File retrieval count
destination.SetFileRetrievalCount("/File1.txt", 52);
LastCall.On(destination).Repeat.Once();
destination.SetFileRetrievalCount("/File2.txt", 0);
LastCall.On(destination).Repeat.Once();
destination.SetFileRetrievalCount("/Dir1/File.txt", 21);
LastCall.On(destination).Repeat.Once();
destination.SetFileRetrievalCount("/Dir1/Sub/File.txt", 123);
LastCall.On(destination).Repeat.Once();
// Page attachments
Expect.Call(destination.StorePageAttachment(null, "Attachment.txt", null, false)).Constraints(
RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "MainPage"; }), RMC.Is.Equal("Attachment.txt"), RMC.Is.TypeOf<Stream>(), RMC.Is.Equal(false)).Do(new StoreAttachment(
delegate(PageInfo page, string name, Stream stream, bool overwrite) {
byte[] buff = new byte[512];
int read = stream.Read(buff, 0, (int)stream.Length);
Assert.AreEqual("content5", Encoding.Unicode.GetString(buff, 0, read), "Wrong data");
return true;
}));
Expect.Call(destination.StorePageAttachment(null, "Attachment2.txt", null, false)).Constraints(
RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Page"; }), RMC.Is.Equal("Attachment2.txt"), RMC.Is.TypeOf<Stream>(), RMC.Is.Equal(false)).Do(new StoreAttachment(
delegate(PageInfo page, string name, Stream stream, bool overwrite) {
byte[] buff = new byte[512];
int read = stream.Read(buff, 0, (int)stream.Length);
Assert.AreEqual("content6", Encoding.Unicode.GetString(buff, 0, read), "Wrong data");
return true;
}));
// Attachment retrieval count
destination.SetPageAttachmentRetrievalCount(null, "Attachment.txt", 8);
LastCall.On(destination).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "MainPage"; }), RMC.Is.Equal("Attachment.txt"), RMC.Is.Equal(8)).Repeat.Once();
destination.SetPageAttachmentRetrievalCount(null, "Attachment2.txt", 29);
LastCall.On(destination).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Page"; }), RMC.Is.Equal("Attachment2.txt"), RMC.Is.Equal(29)).Repeat.Once();
// Delete source content
Expect.Call(source.DeleteFile("/File1.txt")).Return(true);
Expect.Call(source.DeleteFile("/File2.txt")).Return(true);
Expect.Call(source.DeleteDirectory("/Dir1/")).Return(true);
Expect.Call(source.DeleteDirectory("/Dir2/")).Return(true);
Expect.Call(source.DeletePageAttachment(null, "Attachment.aspx")).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "MainPage"; }), RMC.Is.Equal("Attachment.txt")).Return(true);
Expect.Call(source.DeletePageAttachment(null, "Attachment2.aspx")).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Page"; }), RMC.Is.Equal("Attachment2.txt")).Return(true);
mocks.Replay(source);
mocks.Replay(destination);
mocks.Replay(settingsProvider);
mocks.Replay(aclManager);
DataMigrator.MigrateFilesStorageProviderData(source, destination, settingsProvider);
mocks.Verify(source);
mocks.Verify(destination);
mocks.Verify(settingsProvider);
mocks.Verify(aclManager);
}
[Test]
public void CopySettingsStorageProviderData() {
MockRepository mocks = new MockRepository();
ISettingsStorageProviderV30 source = mocks.StrictMock<ISettingsStorageProviderV30>();
ISettingsStorageProviderV30 destination = mocks.StrictMock<ISettingsStorageProviderV30>();
IAclManager sourceAclManager = mocks.StrictMock<IAclManager>();
IAclManager destinationAclManager = mocks.StrictMock<IAclManager>();
// Setup SOURCE ---------------------
// Settings
Dictionary<string, string> settings = new Dictionary<string, string>() {
{ "Set1", "Value1" },
{ "Set2", "Value2" }
};
Expect.Call(source.GetAllSettings()).Return(settings);
// Meta-data (global)
Expect.Call(source.GetMetaDataItem(MetaDataItem.AccountActivationMessage, null)).Return("AAM");
Expect.Call(source.GetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null)).Return("PRM");
Expect.Call(source.GetMetaDataItem(MetaDataItem.LoginNotice, null)).Return("");
Expect.Call(source.GetMetaDataItem(MetaDataItem.PageChangeMessage, null)).Return("PCM");
Expect.Call(source.GetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null)).Return("DCM");
// Meta-data (root)
Expect.Call(source.GetMetaDataItem(MetaDataItem.EditNotice, null)).Return("");
Expect.Call(source.GetMetaDataItem(MetaDataItem.Footer, null)).Return("FOOT");
Expect.Call(source.GetMetaDataItem(MetaDataItem.Header, null)).Return("HEADER");
Expect.Call(source.GetMetaDataItem(MetaDataItem.HtmlHead, null)).Return("HTML");
Expect.Call(source.GetMetaDataItem(MetaDataItem.PageFooter, null)).Return("P_FOOT");
Expect.Call(source.GetMetaDataItem(MetaDataItem.PageHeader, null)).Return("P_HEADER");
Expect.Call(source.GetMetaDataItem(MetaDataItem.Sidebar, null)).Return("SIDEBAR");
// Meta-data ("NS" namespace)
Expect.Call(source.GetMetaDataItem(MetaDataItem.EditNotice, "NS")).Return("NS_EDIT");
Expect.Call(source.GetMetaDataItem(MetaDataItem.Footer, "NS")).Return("NS_FOOT");
Expect.Call(source.GetMetaDataItem(MetaDataItem.Header, "NS")).Return("NS_HEADER");
Expect.Call(source.GetMetaDataItem(MetaDataItem.HtmlHead, "NS")).Return("NS_HTML");
Expect.Call(source.GetMetaDataItem(MetaDataItem.PageFooter, "NS")).Return("NS_P_FOOT");
Expect.Call(source.GetMetaDataItem(MetaDataItem.PageHeader, "NS")).Return("NS_P_HEADER");
Expect.Call(source.GetMetaDataItem(MetaDataItem.Sidebar, "NS")).Return("NS_SIDEBAR");
// Plugin assemblies
byte[] asm1 = new byte[] { 1, 2, 3, 4, 5 };
byte[] asm2 = new byte[] { 6, 7, 8, 9, 10, 11, 12 };
Expect.Call(source.ListPluginAssemblies()).Return(new string[] { "Plugins1.dll", "Plugins2.dll" });
Expect.Call(source.RetrievePluginAssembly("Plugins1.dll")).Return(asm1);
Expect.Call(source.RetrievePluginAssembly("Plugins2.dll")).Return(asm2);
// Plugin status
Expect.Call(source.GetPluginStatus("Test1.Plugin1")).Return(true);
Expect.Call(source.GetPluginStatus("Test2.Plugin2")).Return(false);
// Plugin config
Expect.Call(source.GetPluginConfiguration("Test1.Plugin1")).Return("Config1");
Expect.Call(source.GetPluginConfiguration("Test2.Plugin2")).Return("");
// Outgoing links
Dictionary<string, string[]> outgoingLinks = new Dictionary<string, string[]>() {
{ "Page1", new string[] { "Page2", "Page3" } },
{ "Page2", new string[] { "Page3" } },
{ "Page3", new string[] { "Page4", "Page3" } }
};
Expect.Call(source.GetAllOutgoingLinks()).Return(outgoingLinks);
// ACLs
Expect.Call(source.AclManager).Return(sourceAclManager);
AclEntry[] entries = new AclEntry[] {
new AclEntry("Res1", "Act1", "Subj1", Value.Grant),
new AclEntry("Res2", "Act2", "Subj2", Value.Deny)
};
Expect.Call(sourceAclManager.RetrieveAllEntries()).Return(entries);
// Setup DESTINATION -----------------
// Settings
destination.BeginBulkUpdate();
LastCall.On(destination).Repeat.Once();
foreach(KeyValuePair<string, string> pair in settings) {
Expect.Call(destination.SetSetting(pair.Key, pair.Value)).Return(true);
}
destination.EndBulkUpdate();
LastCall.On(destination).Repeat.Once();
// Meta-data (global)
Expect.Call(destination.SetMetaDataItem(MetaDataItem.AccountActivationMessage, null, "AAM")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null, "PRM")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.LoginNotice, null, "")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.PageChangeMessage, null, "PCM")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null, "DCM")).Return(true);
// Meta-data (root)
Expect.Call(destination.SetMetaDataItem(MetaDataItem.EditNotice, null, "")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.Footer, null, "FOOT")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.Header, null, "HEADER")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.HtmlHead, null, "HTML")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.PageFooter, null, "P_FOOT")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.PageHeader, null, "P_HEADER")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.Sidebar, null, "SIDEBAR")).Return(true);
// Meta-data ("NS" namespace)
Expect.Call(destination.SetMetaDataItem(MetaDataItem.EditNotice, "NS", "NS_EDIT")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.Footer, "NS", "NS_FOOT")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.Header, "NS", "NS_HEADER")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.HtmlHead, "NS", "NS_HTML")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.PageFooter, "NS", "NS_P_FOOT")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.PageHeader, "NS", "NS_P_HEADER")).Return(true);
Expect.Call(destination.SetMetaDataItem(MetaDataItem.Sidebar, "NS", "NS_SIDEBAR")).Return(true);
// Plugin assemblies
Expect.Call(destination.StorePluginAssembly("Plugins1.dll", asm1)).Return(true);
Expect.Call(destination.StorePluginAssembly("Plugins2.dll", asm2)).Return(true);
// Plugin status
Expect.Call(destination.SetPluginStatus("Test1.Plugin1", true)).Return(true);
Expect.Call(destination.SetPluginStatus("Test2.Plugin2", false)).Return(true);
// Plugin config
Expect.Call(destination.SetPluginConfiguration("Test1.Plugin1", "Config1")).Return(true);
Expect.Call(destination.SetPluginConfiguration("Test2.Plugin2", "")).Return(true);
// Outgoing links
foreach(KeyValuePair<string, string[]> pair in outgoingLinks) {
Expect.Call(destination.StoreOutgoingLinks(pair.Key, pair.Value)).Return(true);
}
// ACLs
Expect.Call(destination.AclManager).Return(destinationAclManager).Repeat.Any();
foreach(AclEntry e in entries) {
Expect.Call(destinationAclManager.StoreEntry(e.Resource, e.Action, e.Subject, e.Value)).Return(true);
}
mocks.ReplayAll();
DataMigrator.CopySettingsStorageProviderData(source, destination,
new string[] { "NS" }, new string[] { "Test1.Plugin1", "Test2.Plugin2" });
mocks.VerifyAll();
}
}
}

View file

@ -0,0 +1,30 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using NUnit.Framework;
using Rhino.Mocks;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki.Tests {
public class FilesStorageProviderTests : FilesStorageProviderTestScaffolding {
public override IFilesStorageProviderV30 GetProvider() {
FilesStorageProvider prov = new FilesStorageProvider();
prov.Init(MockHost(), "");
return prov;
}
[Test]
public void Init() {
IFilesStorageProviderV30 prov = GetProvider();
prov.Init(MockHost(), "");
Assert.IsNotNull(prov.Information, "Information should not be null");
}
}
}

View file

@ -0,0 +1,167 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Rhino.Mocks;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki.Tests {
[TestFixture]
public class FormatterTests {
private MockRepository mocks;
[Test]
public void Format() {
FormattingContext context = FormattingContext.PageContent;
PageInfo currentPage = null;
string[] linkedPages = null;
string output = Formatter.Format(Input, false, context, currentPage, out linkedPages, false);
// Ignore \r characters
Assert.AreEqual(ExpectedOutput.Replace("\r", ""), output.Replace("\r", ""), "Formatter output is different from expected output");
}
[SetUp]
public void SetUp() {
mocks = new MockRepository();
ISettingsStorageProviderV30 settingsProvider = mocks.StrictMock<ISettingsStorageProviderV30>();
Expect.Call(settingsProvider.GetSetting("ProcessSingleLineBreaks")).Return("false").Repeat.Any();
Collectors.SettingsProvider = settingsProvider;
IPagesStorageProviderV30 pagesProvider = mocks.StrictMock<IPagesStorageProviderV30>();
Collectors.PagesProviderCollector = new ProviderCollector<IPagesStorageProviderV30>();
Collectors.PagesProviderCollector.AddProvider(pagesProvider);
Expect.Call(settingsProvider.GetSetting("DefaultPagesProvider")).Return(pagesProvider.GetType().FullName).Repeat.Any();
PageInfo page1 = new PageInfo("page1", pagesProvider, DateTime.Now);
PageContent page1Content = new PageContent(page1, "Page 1", "User", DateTime.Now, "Comment", "Content", null, null);
Expect.Call(pagesProvider.GetPage("page1")).Return(page1).Repeat.Any();
Expect.Call(pagesProvider.GetContent(page1)).Return(page1Content).Repeat.Any();
Expect.Call(pagesProvider.GetPage("page2")).Return(null).Repeat.Any();
//Pages.Instance = new Pages();
Host.Instance = new Host();
Expect.Call(settingsProvider.GetSetting("CacheSize")).Return("100").Repeat.Any();
Expect.Call(settingsProvider.GetSetting("CacheCutSize")).Return("20").Repeat.Any();
Expect.Call(settingsProvider.GetSetting("DefaultCacheProvider")).Return(typeof(CacheProvider).FullName).Repeat.Any();
// Cache needs setting to init
mocks.Replay(settingsProvider);
ICacheProviderV30 cacheProvider = new CacheProvider();
cacheProvider.Init(Host.Instance, "");
Collectors.CacheProviderCollector = new ProviderCollector<ICacheProviderV30>();
Collectors.CacheProviderCollector.AddProvider(cacheProvider);
mocks.Replay(pagesProvider);
Collectors.FormatterProviderCollector = new ProviderCollector<IFormatterProviderV30>();
//System.Web.UI.HtmlTextWriter writer = new System.Web.UI.HtmlTextWriter(new System.IO.StreamWriter(new System.IO.MemoryStream()));
//System.Web.Hosting.SimpleWorkerRequest request = new System.Web.Hosting.SimpleWorkerRequest("Default.aspx", "?Page=MainPage", writer);
System.Web.HttpContext.Current = new System.Web.HttpContext(new DummyRequest());
}
[TearDown]
public void TearDown() {
mocks.VerifyAll();
}
private const string Input =
@"'''bold''' ''italic'' __underlined__ --striked--
[page1] [page2|title]
@@* item 1
* item 2
second line@@
{|
| cell || other cell
|}";
private const string ExpectedOutput =
@"<b>bold</b> <i>italic</i> <u>underlined</u> <strike>striked</strike>
<a class=""pagelink"" href=""page1.ashx"" title=""Page 1"">page1</a> <a class=""unknownlink"" href=""page2.ashx"" title=""page2"">title</a><br /><br /><pre>&#0042; item 1
&#0042; item 2
second line</pre><br /><table><tr><td>cell</td><td>other cell</td></tr></table>
";
}
public class DummyRequest : System.Web.HttpWorkerRequest {
public override void EndOfRequest() {
}
public override void FlushResponse(bool finalFlush) {
}
public override string GetHttpVerbName() {
return "GET";
}
public override string GetHttpVersion() {
return "1.1";
}
public override string GetLocalAddress() {
return "http://localhost/";
}
public override int GetLocalPort() {
return 80;
}
public override string GetQueryString() {
return "";
}
public override string GetRawUrl() {
return "http://localhost/Default.aspx";
}
public override string GetRemoteAddress() {
return "127.0.0.1";
}
public override int GetRemotePort() {
return 45695;
}
public override string GetUriPath() {
return "/";
}
public override void SendKnownResponseHeader(int index, string value) {
}
public override void SendResponseFromFile(IntPtr handle, long offset, long length) {
}
public override void SendResponseFromFile(string filename, long offset, long length) {
}
public override void SendResponseFromMemory(byte[] data, int length) {
}
public override void SendStatus(int statusCode, string statusDescription) {
}
public override void SendUnknownResponseHeader(string name, string value) {
}
}
}

View file

@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ScrewTurn.Wiki.SearchEngine.Tests;
using NUnit.Framework;
using ScrewTurn.Wiki.SearchEngine;
namespace ScrewTurn.Wiki.Tests {
[TestFixture]
public class IndexStorerTests : TestsBase {
private string documentsFile = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "__SE_Documents.dat");
private string wordsFile = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "__SE_Words.dat");
private string mappingsFile = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "__SE_Mappings.dat");
[TearDown]
public void RemoveFiles() {
try {
File.Delete(documentsFile);
}
catch { }
try {
File.Delete(wordsFile);
}
catch { }
try {
File.Delete(mappingsFile);
}
catch { }
}
[Test]
public void Constructor() {
IInMemoryIndex index = MockInMemoryIndex();
IndexStorer storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index);
Assert.AreEqual(index, storer.Index, "Wrong index");
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void Constructor_InvalidDocumentsFile(string file) {
IndexStorer storer = new IndexStorer(file, wordsFile, mappingsFile, MockInMemoryIndex());
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void Constructor_InvalidWordsFile(string file) {
IndexStorer storer = new IndexStorer(documentsFile, file, mappingsFile, MockInMemoryIndex());
}
[TestCase(null, ExpectedException = typeof(ArgumentNullException))]
[TestCase("", ExpectedException = typeof(ArgumentException))]
public void Constructor_InvalidMappingsFile(string file) {
IndexStorer storer = new IndexStorer(documentsFile, wordsFile, file, MockInMemoryIndex());
}
[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void Constructor_NullIndex() {
IndexStorer storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, null);
}
[Test]
public void Size() {
IInMemoryIndex index = MockInMemoryIndex();
index.SetBuildDocumentDelegate(delegate(DumpedDocument d) { return null; });
IndexStorer storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index);
storer.LoadIndex();
Assert.IsTrue(storer.Size > 0, "Size should be greater than zero");
}
[Test]
public void LoadIndexAndModifications() {
IInMemoryIndex index = MockInMemoryIndex();
IDocument doc1 = MockDocument("doc1", "Document", "doc", DateTime.Now);
IDocument doc2 = MockDocument2("doc2", "Article", "doc", DateTime.Now);
BuildDocument buildDoc = new BuildDocument(delegate(DumpedDocument d) {
return d.Name == doc1.Name ? doc1 : doc2;
});
index.SetBuildDocumentDelegate(buildDoc);
IndexStorer storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index);
storer.LoadIndex();
index.StoreDocument(doc1, null, "", null);
index.StoreDocument(doc2, null, "", null);
Assert.AreEqual(2, index.TotalDocuments, "Wrong document count");
Assert.AreEqual(12, index.TotalWords, "Wrong word count");
Assert.AreEqual(12, index.TotalOccurrences, "Wrong occurrence count");
storer.Dispose();
storer = null;
index = MockInMemoryIndex();
index.SetBuildDocumentDelegate(buildDoc);
storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index);
storer.LoadIndex();
Assert.AreEqual(2, index.TotalDocuments, "Wrong document count");
Assert.AreEqual(12, index.TotalWords, "Wrong word count");
Assert.AreEqual(12, index.TotalOccurrences, "Wrong occurrence count");
SearchResultCollection res = index.Search(new SearchParameters("document content article"));
Assert.AreEqual(2, res.Count, "Wrong result count");
Assert.AreEqual(2, res[0].Matches.Count, "Wrong matches count");
Assert.AreEqual(1, res[1].Matches.Count, "Wrong matches count");
Assert.AreEqual("document", res[0].Matches[0].Text, "Wrong match text");
Assert.AreEqual(0, res[0].Matches[0].FirstCharIndex, "Wrong match first char index");
Assert.AreEqual(0, res[0].Matches[0].WordIndex, "Wrong match word index");
Assert.AreEqual(WordLocation.Title, res[0].Matches[0].Location, "Wrong match location");
Assert.AreEqual("content", res[0].Matches[1].Text, "Wrong match text");
Assert.AreEqual(13, res[0].Matches[1].FirstCharIndex, "Wrong match first char index");
Assert.AreEqual(3, res[0].Matches[1].WordIndex, "Wrong match word index");
Assert.AreEqual(WordLocation.Content, res[0].Matches[1].Location, "Wrong match location");
Assert.AreEqual("article", res[1].Matches[0].Text, "Wrong match text");
Assert.AreEqual(0, res[1].Matches[0].FirstCharIndex, "Wrong match first char index");
Assert.AreEqual(0, res[1].Matches[0].WordIndex, "Wrong match word index");
Assert.AreEqual(WordLocation.Title, res[1].Matches[0].Location, "Wrong match location");
index.RemoveDocument(doc1, null);
storer.Dispose();
storer = null;
index = MockInMemoryIndex();
index.SetBuildDocumentDelegate(buildDoc);
storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index);
storer.LoadIndex();
Assert.AreEqual(1, index.TotalDocuments, "Wrong document count");
Assert.AreEqual(7, index.TotalWords, "Wrong word count");
Assert.AreEqual(7, index.TotalOccurrences, "Wrong occurrence count");
res = index.Search(new SearchParameters("document content article"));
Assert.AreEqual(1, res.Count, "Wrong result count");
Assert.AreEqual(1, res[0].Matches.Count, "Wrong matches count");
Assert.AreEqual("article", res[0].Matches[0].Text, "Wrong match text");
Assert.AreEqual(0, res[0].Matches[0].FirstCharIndex, "Wrong match first char index");
Assert.AreEqual(0, res[0].Matches[0].WordIndex, "Wrong match word index");
Assert.AreEqual(WordLocation.Title, res[0].Matches[0].Location, "Wrong match location");
index.Clear(null);
storer.Dispose();
storer = null;
index = MockInMemoryIndex();
index.SetBuildDocumentDelegate(buildDoc);
storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index);
storer.LoadIndex();
Assert.AreEqual(0, index.TotalDocuments, "Wrong document count");
Assert.AreEqual(0, index.TotalWords, "Wrong word count");
Assert.AreEqual(0, index.TotalOccurrences, "Wrong occurrence count");
Assert.AreEqual(0, index.Search(new SearchParameters("document")).Count, "Wrong result count");
}
[Test]
public void DataCorrupted() {
IInMemoryIndex index = MockInMemoryIndex();
IDocument doc1 = MockDocument("doc1", "Document", "doc", DateTime.Now);
IDocument doc2 = MockDocument2("doc2", "Article", "doc", DateTime.Now);
index.SetBuildDocumentDelegate(delegate(DumpedDocument d) { return d.Name == doc1.Name ? doc1 : doc2; });
IndexStorer storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index);
storer.LoadIndex();
index.StoreDocument(doc1, null, "", null);
index.StoreDocument(doc2, null, "", null);
Assert.AreEqual(2, index.TotalDocuments, "Wrong document count");
Assert.AreEqual(12, index.TotalWords, "Wrong word count");
Assert.AreEqual(12, index.TotalOccurrences, "Wrong occurrence count");
storer.Dispose();
storer = null;
File.Create(documentsFile).Close();
index = MockInMemoryIndex();
storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index);
storer.LoadIndex();
Assert.IsTrue(storer.DataCorrupted, "DataCorrupted should be true");
Assert.AreEqual(0, index.TotalDocuments, "Wrong document count");
}
[Test]
public void ReplaceDocument() {
// This test checks that IndexStorer properly discards the old document when a new copy is stored,
// even when the title and date/time are different
IInMemoryIndex index = MockInMemoryIndex();
DateTime dt1 = DateTime.Now.AddDays(-1);
IDocument doc1 = MockDocument("doc1", "Document", "doc", dt1);
IDocument doc2 = MockDocument2("doc1", "Article", "doc", DateTime.Now);
index.SetBuildDocumentDelegate(delegate(DumpedDocument d) { return d.DateTime == dt1 ? doc1 : doc2; });
IndexStorer storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index);
storer.LoadIndex();
index.StoreDocument(doc1, null, "", null);
Assert.AreEqual(1, index.TotalDocuments, "Wrong document count");
index.StoreDocument(doc2, null, "", null);
Assert.AreEqual(1, index.TotalDocuments, "Wrong document count");
storer.Dispose();
storer = null;
index = MockInMemoryIndex();
index.SetBuildDocumentDelegate(delegate(DumpedDocument d) { return d.DateTime == dt1 ? doc1 : doc2; });
storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index);
storer.LoadIndex();
Assert.AreEqual(1, index.TotalDocuments, "Wrong document count");
}
}
}

View file

@ -0,0 +1,163 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using NUnit.Framework;
using Rhino.Mocks;
namespace ScrewTurn.Wiki.Tests {
public class PagesStorageProviderTests : PagesStorageProviderTestScaffolding {
public override IPagesStorageProviderV30 GetProvider() {
PagesStorageProvider prov = new PagesStorageProvider();
prov.Init(MockHost(), "");
return prov;
}
[Test]
public void Init() {
IPagesStorageProviderV30 prov = GetProvider();
prov.Init(MockHost(), "");
Assert.IsNotNull(prov.Information, "Information should not be null");
}
[Test]
public void Init_Upgrade() {
string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString());
Directory.CreateDirectory(testDir);
MockRepository mocks = new MockRepository();
IHostV30 host = mocks.DynamicMock<IHostV30>();
Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce();
Expect.Call(host.UpgradePageStatusToAcl(null, 'L')).IgnoreArguments().Repeat.Twice().Return(true);
mocks.Replay(host);
string file = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Pages.cs");
string categoriesFile = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Categories.cs");
string navPathsFile = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "NavigationPaths.cs");
string directory = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Pages");
string messagesDirectory = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Messages");
Directory.CreateDirectory(directory);
Directory.CreateDirectory(messagesDirectory);
// Structure (Keywords and Description are new in v3)
// Page Title
// Username|DateTime[|Comment] --- Comment is optional
// ##PAGE##
// Content...
File.WriteAllText(Path.Combine(directory, "Page1.cs"), "Title1\r\nSYSTEM|2008/10/30 20:20:20|Comment\r\n##PAGE##\r\nContent...");
File.WriteAllText(Path.Combine(directory, "Page2.cs"), "Title2\r\nSYSTEM|2008/10/30 20:20:20\r\n##PAGE\r\nContent. [[Page.3]] [Page.3|Link to update].");
File.WriteAllText(Path.Combine(directory, "Page.3.cs"), "Title3\r\nSYSTEM|2008/10/30 20:20:20|Comment\r\n##PAGE\r\nContent...");
// ID|Username|Subject|DateTime|ParentID|Body
File.WriteAllText(Path.Combine(messagesDirectory, "Page.3.cs"), "0|User|Hello|2008/10/30 21:21:21|-1|Blah\r\n");
// Structure
// [Namespace.]PageName|PageFile|Status|DateTime
File.WriteAllText(file, "Page1|Page1.cs|NORMAL|2008/10/30 20:20:20\r\nPage2|Page2.cs|PUBLIC\r\nPage.3|Page.3.cs|LOCKED");
File.WriteAllText(categoriesFile, "Cat1|Page.3\r\nCat.2|Page1|Page2\r\n");
File.WriteAllText(navPathsFile, "Path1|Page1|Page.3\r\nPath2|Page2\r\n");
PagesStorageProvider prov = new PagesStorageProvider();
prov.Init(host, "");
PageInfo[] pages = prov.GetPages(null);
Assert.AreEqual(3, pages.Length, "Wrong page count");
Assert.AreEqual("Page1", pages[0].FullName, "Wrong name");
Assert.AreEqual("Page2", pages[1].FullName, "Wrong name");
Assert.AreEqual("Page_3", pages[2].FullName, "Wrong name");
//Assert.IsFalse(prov.GetContent(pages[1]).Content.Contains("Page.3"), "Content should not contain 'Page.3'");
//Assert.IsTrue(prov.GetContent(pages[1]).Content.Contains("Page_3"), "Content should contain 'Page_3'");
Message[] messages = prov.GetMessages(pages[2]);
Assert.AreEqual(1, messages.Length, "Wrong message count");
Assert.AreEqual("Hello", messages[0].Subject, "Wrong subject");
CategoryInfo[] categories = prov.GetCategories(null);
Assert.AreEqual(2, categories.Length, "Wrong category count");
Assert.AreEqual("Cat1", categories[0].FullName, "Wrong name");
Assert.AreEqual(1, categories[0].Pages.Length, "Wrong page count");
Assert.AreEqual("Page_3", categories[0].Pages[0], "Wrong page");
Assert.AreEqual("Cat_2", categories[1].FullName, "Wrong name");
Assert.AreEqual(2, categories[1].Pages.Length, "Wrong page count");
Assert.AreEqual("Page1", categories[1].Pages[0], "Wrong page");
Assert.AreEqual("Page2", categories[1].Pages[1], "Wrong page");
NavigationPath[] navPaths = prov.GetNavigationPaths(null);
Assert.AreEqual(2, navPaths.Length, "Wrong nav path count");
Assert.AreEqual("Path1", navPaths[0].FullName, "Wrong name");
Assert.AreEqual(2, navPaths[0].Pages.Length, "Wrong page count");
Assert.AreEqual("Page1", navPaths[0].Pages[0], "Wrong page");
Assert.AreEqual("Page_3", navPaths[0].Pages[1], "Wrong page");
Assert.AreEqual(1, navPaths[1].Pages.Length, "Wrong page count");
Assert.AreEqual("Page2", navPaths[1].Pages[0], "Wrong page");
mocks.Verify(host);
// Simulate another startup - upgrade not needed anymore
mocks.BackToRecord(host);
Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce();
Expect.Call(host.UpgradePageStatusToAcl(null, 'L')).IgnoreArguments().Repeat.Times(0).Return(false);
mocks.Replay(host);
prov = new PagesStorageProvider();
prov.Init(host, "");
mocks.Verify(host);
Directory.Delete(testDir, true);
}
/*[Test]
public void Init_UpgradeCategories() {
string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString());
Directory.CreateDirectory(testDir);
MockRepository mocks = new MockRepository();
IHost host = mocks.DynamicMock<IHost>();
Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce();
mocks.Replay(host);
string file = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Categories.cs");
string filePages = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Pages.cs");
string directory = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Pages");
Directory.CreateDirectory(directory);
File.WriteAllText(file, "Cat1|Page1\r\nCat.2|Page2|Page3\r\n");
File.WriteAllText(Path.Combine(directory, "Page1.cs"), "Title1\r\nSYSTEM|2008/10/30 20:20:20|Comment\r\n##PAGE##\r\nContent...");
File.WriteAllText(Path.Combine(directory, "Page2.cs"), "Title2\r\nSYSTEM|2008/10/30 20:20:20\r\n##PAGE\r\nContent...");
File.WriteAllText(Path.Combine(directory, "Page3.cs"), "Title3\r\nSYSTEM|2008/10/30 20:20:20|Comment\r\n##PAGE\r\nContent...");
File.WriteAllText(filePages, "Page1|Page1.cs|NORMAL|2008/10/30 20:20:20\r\nPage2|Page2.cs\r\nPage3|Page3.cs|LOCKED");
PagesStorageProvider prov = new PagesStorageProvider();
prov.Init(host, "");
CategoryInfo[] categories = prov.GetCategories(null);
Assert.AreEqual("Cat1", categories[0].FullName, "Wrong name");
Assert.AreEqual("Cat_2", categories[1].FullName, "Wrong name");
Assert.AreEqual(2, categories.Length, "Wrong page count");
mocks.Verify(host);
}*/
}
}

View file

@ -0,0 +1,18 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ScrewTurn Wiki Core Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c0deeef4-19d7-4b11-9255-f5d7e9ddd16e")]

View file

@ -0,0 +1,30 @@

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki.Tests {
[TestFixture]
public class ProviderLoaderTests {
[TestCase(null, typeof(SettingsStorageProvider))]
[TestCase("", typeof(SettingsStorageProvider))]
[TestCase("default", typeof(SettingsStorageProvider))]
[TestCase("DEfaulT", typeof(SettingsStorageProvider))]
[TestCase("ScrewTurn.Wiki.SettingsStorageProvider, ScrewTurn.Wiki.Core", typeof(SettingsStorageProvider))]
[TestCase("ScrewTurn.Wiki.SettingsStorageProvider, ScrewTurn.Wiki.Core.dll", typeof(SettingsStorageProvider))]
[TestCase("ScrewTurn.Wiki.Tests.TestSettingsStorageProvider, ScrewTurn.Wiki.Core.Tests.dll", typeof(TestSettingsStorageProvider))]
[TestCase("glglglglglglg, gfgfgfgfggf.dll", typeof(string), ExpectedException = typeof(ArgumentException))]
public void Static_LoadSettingsStorageProvider(string p, Type type) {
ISettingsStorageProviderV30 prov = ProviderLoader.LoadSettingsStorageProvider(p);
Assert.IsNotNull(prov, "Provider should not be null");
// type == prov.GetType() seems to fail due to reflection
Assert.AreEqual(type.ToString(), prov.GetType().FullName, "Wrong return type");
}
}
}

View file

@ -0,0 +1,30 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using NUnit.Framework;
using Rhino.Mocks;
namespace ScrewTurn.Wiki.Tests {
public class SettingsStorageProviderTests : SettingsStorageProviderTestScaffolding {
public override ISettingsStorageProviderV30 GetProvider() {
SettingsStorageProvider prov = new SettingsStorageProvider();
prov.Init(MockHost(), "");
return prov;
}
[Test]
public void Init() {
ISettingsStorageProviderV30 prov = GetProvider();
prov.Init(MockHost(), "");
Assert.IsNotNull(prov.Information, "Information should not be null");
}
}
}

View file

@ -0,0 +1,147 @@

using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using ScrewTurn.Wiki.AclEngine;
namespace ScrewTurn.Wiki.Tests {
/// <summary>
/// Implements a dummy Settings Storage Provider to use for testing.
/// </summary>
public class TestSettingsStorageProvider : ISettingsStorageProviderV30 {
public string GetSetting(string name) {
throw new NotImplementedException();
}
public bool SetSetting(string name, string value) {
throw new NotImplementedException();
}
public void BeginBulkUpdate() {
throw new NotImplementedException();
}
public void EndBulkUpdate() {
throw new NotImplementedException();
}
public void LogEntry(string message, EntryType entryType, string user) {
throw new NotImplementedException();
}
public LogEntry[] GetLogEntries() {
throw new NotImplementedException();
}
public void ClearLog() {
throw new NotImplementedException();
}
public void CutLog(int size) {
throw new NotImplementedException();
}
public int LogSize {
get { throw new NotImplementedException(); }
}
public string GetMetaDataItem(MetaDataItem item, string tag) {
throw new NotImplementedException();
}
public bool SetMetaDataItem(MetaDataItem item, string tag, string content) {
throw new NotImplementedException();
}
public RecentChange[] GetRecentChanges() {
throw new NotImplementedException();
}
public bool AddRecentChange(string page, string title, string messageSubject, DateTime dateTime, string user, ScrewTurn.Wiki.PluginFramework.Change change, string descr) {
throw new NotImplementedException();
}
public void Init(IHostV30 host, string config) {
throw new NotImplementedException();
}
public void Shutdown() {
throw new NotImplementedException();
}
public ComponentInformation Information {
get { throw new NotImplementedException(); }
}
public string ConfigHelpHtml {
get { throw new NotImplementedException(); }
}
public string[] ListPluginAssemblies() {
throw new NotImplementedException();
}
public bool StorePluginAssembly(string filename, byte[] assembly) {
throw new NotImplementedException();
}
public byte[] RetrievePluginAssembly(string filename) {
throw new NotImplementedException();
}
public bool DeletePluginAssembly(string filename) {
throw new NotImplementedException();
}
public bool SetPluginStatus(string typeName, bool enabled) {
throw new NotImplementedException();
}
public bool GetPluginStatus(string typeName) {
throw new NotImplementedException();
}
public bool SetPluginConfiguration(string typeName, string config) {
throw new NotImplementedException();
}
public string GetPluginConfiguration(string typeName) {
throw new NotImplementedException();
}
public IAclManager AclManager {
get {
throw new NotImplementedException();
}
}
public bool StoreOutgoingLinks(string page, string[] outgoingLinks) {
throw new NotImplementedException();
}
public string[] GetOutgoingLinks(string page) {
throw new NotImplementedException();
}
public IDictionary<string, string[]> GetAllOutgoingLinks() {
throw new NotImplementedException();
}
public bool DeleteOutgoingLinks(string page) {
throw new NotImplementedException();
}
public bool UpdateOutgoingLinksForRename(string oldName, string newName) {
throw new NotImplementedException();
}
public IDictionary<string, string> GetAllSettings() {
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,75 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using NUnit.Framework;
using Rhino.Mocks;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki.Tests {
public class UsersStorageProviderTests : UsersStorageProviderTestScaffolding {
public override IUsersStorageProviderV30 GetProvider() {
UsersStorageProvider prov = new UsersStorageProvider();
prov.Init(MockHost(), "");
return prov;
}
[Test]
public void Init() {
IUsersStorageProviderV30 prov = GetProvider();
prov.Init(MockHost(), "");
Assert.IsNotNull(prov.Information, "Information should not be null");
}
[Test]
public void Init_Upgrade() {
string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString());
Directory.CreateDirectory(testDir);
MockRepository mocks = new MockRepository();
IHostV30 host = mocks.DynamicMock<IHostV30>();
Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce();
Expect.Call(host.GetSettingValue(SettingName.AdministratorsGroup)).Return("Administrators").Repeat.Once();
Expect.Call(host.GetSettingValue(SettingName.UsersGroup)).Return("Users").Repeat.Once();
Expect.Call(host.UpgradeSecurityFlagsToGroupsAcl(null, null)).IgnoreArguments().Repeat.Times(1).Return(true);
mocks.Replay(host);
string file = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Users.cs");
File.WriteAllText(file, "user|PASSHASH|user@users.com|Inactive|2008/10/31 15:15:15|USER\r\nsuperuser|SUPERPASSHASH|superuser@users.com|Active|2008/10/31 15:15:16|ADMIN");
UsersStorageProvider prov = new UsersStorageProvider();
prov.Init(host, "");
UserInfo[] users = prov.GetUsers();
Assert.AreEqual(2, users.Length, "Wrong user count");
Assert.AreEqual("superuser", users[0].Username, "Wrong username");
Assert.AreEqual("superuser@users.com", users[0].Email, "Wrong email");
Assert.IsTrue(users[0].Active, "User should be active");
Assert.AreEqual("2008/10/31 15:15:16", users[0].DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss"), "Wrong date/time");
Assert.AreEqual(1, users[0].Groups.Length, "Wrong user count");
Assert.AreEqual("Administrators", users[0].Groups[0], "Wrong group");
Assert.AreEqual("user", users[1].Username, "Wrong username");
Assert.AreEqual("user@users.com", users[1].Email, "Wrong email");
Assert.IsFalse(users[1].Active, "User should be inactive");
Assert.AreEqual("2008/10/31 15:15:15", users[1].DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss"), "Wrong date/time");
Assert.AreEqual(1, users[1].Groups.Length, "Wrong user count");
Assert.AreEqual("Users", users[1].Groups[0], "Wrong group");
mocks.Verify(host);
Directory.Delete(testDir, true);
}
}
}

116
Core/AclStorer.cs Normal file
View file

@ -0,0 +1,116 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ScrewTurn.Wiki.AclEngine;
namespace ScrewTurn.Wiki {
/// <summary>
/// Implements a file-based ACL Storer.
/// </summary>
public class AclStorer : AclStorerBase {
private string file;
/// <summary>
/// Initializes a new instance of the <see cref="T:AclStorer" /> class.
/// </summary>
/// <param name="aclManager">The instance of the ACL Manager to handle.</param>
/// <param name="file">The storage file.</param>
public AclStorer(IAclManager aclManager, string file)
: base(aclManager) {
if(file == null) throw new ArgumentNullException("file");
if(file.Length == 0) throw new ArgumentException("File cannot be empty", "file");
this.file = file;
}
/// <summary>
/// Loads data from storage.
/// </summary>
/// <returns>The loaded ACL entries.</returns>
protected override AclEntry[] LoadDataInternal() {
lock(this) {
if(!File.Exists(file)) {
File.Create(file).Close();
return new AclEntry[0];
}
// Format
// Resource|Action|Subject|(1|0)
string[] lines = File.ReadAllLines(file);
AclEntry[] result = new AclEntry[lines.Length];
string[] fields;
for(int i = 0; i < lines.Length; i++) {
fields = lines[i].Split('|');
result[i] = new AclEntry(fields[0], fields[1], fields[2], (fields[3] == "1" ? Value.Grant : Value.Deny));
}
return result;
}
}
/// <summary>
/// Dumps a <see cref="T:AclEntry" /> into a string.
/// </summary>
/// <param name="entry">The entry to dump.</param>
/// <returns>The resulting string.</returns>
private static string DumpAclEntry(AclEntry entry) {
return string.Format("{0}|{1}|{2}|{3}", entry.Resource, entry.Action, entry.Subject, (entry.Value == Value.Grant ? "1" : "0"));
}
/// <summary>
/// Deletes some entries.
/// </summary>
/// <param name="entries">The entries to delete.</param>
protected override void DeleteEntries(AclEntry[] entries) {
lock(this) {
AclEntry[] allEntries = LoadDataInternal();
StringBuilder sb = new StringBuilder(10000);
foreach(AclEntry originalEntry in allEntries) {
// If the current entry is not contained in the entries array, then preserve it
bool delete = false;
foreach(AclEntry entryToDelete in entries) {
if(AclEntry.Equals(originalEntry, entryToDelete)) {
delete = true;
break;
}
}
if(!delete) {
sb.Append(DumpAclEntry(originalEntry));
sb.Append("\r\n");
}
}
File.WriteAllText(file, sb.ToString());
}
}
/// <summary>
/// Stores some entries.
/// </summary>
/// <param name="entries">The entries to store.</param>
protected override void StoreEntries(AclEntry[] entries) {
lock(this) {
StringBuilder sb = new StringBuilder(100);
foreach(AclEntry entry in entries) {
sb.Append(DumpAclEntry(entry));
sb.Append("\r\n");
}
File.AppendAllText(file, sb.ToString());
}
}
}
}

242
Core/AuthChecker.cs Normal file
View file

@ -0,0 +1,242 @@
using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using ScrewTurn.Wiki.AclEngine;
namespace ScrewTurn.Wiki {
/// <summary>
/// Utility class for checking permissions and authorizations.
/// </summary>
/// <remarks>All the methods in this class implement a security bypass for the <i>admin</i> user.</remarks>
public static class AuthChecker {
/// <summary>
/// Gets the settings storage provider.
/// </summary>
private static ISettingsStorageProviderV30 SettingsProvider {
get { return Collectors.SettingsProvider; }
}
/// <summary>
/// Checks whether an action is allowed for the global resources.
/// </summary>
/// <param name="action">The action the user is attempting to perform.</param>
/// <param name="currentUser">The current user.</param>
/// <param name="groups">The groups the user is member of.</param>
/// <returns><c>true</c> if the action is allowed.</returns>
public static bool CheckActionForGlobals(string action, string currentUser, string[] groups) {
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(!AuthTools.IsValidAction(action, Actions.ForGlobals.All)) throw new ArgumentException("Invalid action", "action");
if(currentUser == null) throw new ArgumentNullException("currentUser");
if(currentUser.Length == 0) throw new ArgumentException("Current User cannot be empty", "currentUser");
if(groups == null) throw new ArgumentNullException("groups");
if(currentUser == "admin") return true;
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(Actions.ForGlobals.ResourceMasterPrefix);
Authorization auth = AclEvaluator.AuthorizeAction(Actions.ForGlobals.ResourceMasterPrefix, action,
AuthTools.PrepareUsername(currentUser), AuthTools.PrepareGroups(groups), entries);
return auth == Authorization.Granted;
}
/// <summary>
/// Checks whether an action is allowed for a namespace.
/// </summary>
/// <param name="nspace">The current namespace (<c>null</c> for the root).</param>
/// <param name="action">The action the user is attempting to perform.</param>
/// <param name="currentUser">The current user.</param>
/// <param name="groups">The groups the user is member of.</param>
/// <returns><c>true</c> if the action is allowed, <c>false</c> otherwise.</returns>
public static bool CheckActionForNamespace(NamespaceInfo nspace, string action, string currentUser, string[] groups) {
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(!AuthTools.IsValidAction(action, Actions.ForNamespaces.All)) throw new ArgumentException("Invalid action", "action");
if(currentUser == null) throw new ArgumentNullException("currentUser");
if(currentUser.Length == 0) throw new ArgumentException("Current User cannot be empty", "currentUser");
if(groups == null) throw new ArgumentNullException("groups");
if(currentUser == "admin") return true;
string namespaceName = nspace != null ? nspace.Name : "";
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(
Actions.ForNamespaces.ResourceMasterPrefix + namespaceName);
Authorization auth = AclEvaluator.AuthorizeAction(Actions.ForNamespaces.ResourceMasterPrefix + namespaceName,
action, AuthTools.PrepareUsername(currentUser), AuthTools.PrepareGroups(groups), entries);
if(auth != Authorization.Unknown) return auth == Authorization.Granted;
// Try local escalators
string[] localEscalators = null;
if(Actions.ForNamespaces.LocalEscalators.TryGetValue(action, out localEscalators)) {
foreach(string localAction in localEscalators) {
bool authorized = CheckActionForNamespace(nspace, localAction, currentUser, groups);
if(authorized) return true;
}
}
// Try root escalation
if(nspace != null) {
bool authorized = CheckActionForNamespace(null, action, currentUser, groups);
if(authorized) return true;
}
// Try global escalators
string[] globalEscalators = null;
if(Actions.ForNamespaces.GlobalEscalators.TryGetValue(action, out globalEscalators)) {
foreach(string globalAction in globalEscalators) {
bool authorized = CheckActionForGlobals(globalAction, currentUser, groups);
if(authorized) return true;
}
}
return false;
}
/// <summary>
/// Checks whether an action is allowed for a page.
/// </summary>
/// <param name="page">The current page.</param>
/// <param name="action">The action the user is attempting to perform.</param>
/// <param name="currentUser">The current user.</param>
/// <param name="groups">The groups the user is member of.</param>
/// <returns><c>true</c> if the action is allowed, <c>false</c> otherwise.</returns>
public static bool CheckActionForPage(PageInfo page, string action, string currentUser, string[] groups) {
if(page == null) throw new ArgumentNullException("page");
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(!AuthTools.IsValidAction(action, Actions.ForPages.All)) throw new ArgumentException("Invalid action", "action");
if(currentUser == null) throw new ArgumentNullException("currentUser");
if(currentUser.Length == 0) throw new ArgumentException("Current User cannot be empty", "currentUser");
if(groups == null) throw new ArgumentNullException("groups");
if(currentUser == "admin") return true;
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(Actions.ForPages.ResourceMasterPrefix + page.FullName);
Authorization auth = AclEvaluator.AuthorizeAction(Actions.ForPages.ResourceMasterPrefix + page.FullName, action,
AuthTools.PrepareUsername(currentUser), AuthTools.PrepareGroups(groups), entries);
if(auth != Authorization.Unknown) return auth == Authorization.Granted;
// Try local escalators
string[] localEscalators = null;
if(Actions.ForPages.LocalEscalators.TryGetValue(action, out localEscalators)) {
foreach(string localAction in localEscalators) {
bool authorized = CheckActionForPage(page, localAction, currentUser, groups);
if(authorized) return true;
}
}
// Try namespace escalators
string[] namespaceEscalators = null;
string nsName = NameTools.GetNamespace(page.FullName);
NamespaceInfo ns = string.IsNullOrEmpty(nsName) ? null : new NamespaceInfo(nsName, null, null);
if(Actions.ForPages.NamespaceEscalators.TryGetValue(action, out namespaceEscalators)) {
foreach(string namespaceAction in namespaceEscalators) {
bool authorized = CheckActionForNamespace(ns, namespaceAction, currentUser, groups);
if(authorized) return true;
}
}
// Try global escalators
string[] globalEscalators = null;
if(Actions.ForPages.GlobalEscalators.TryGetValue(action, out globalEscalators)) {
foreach(string globalAction in globalEscalators) {
bool authorized = CheckActionForGlobals(globalAction, currentUser, groups);
if(authorized) return true;
}
}
return false;
}
/// <summary>
/// Checks whether an action is allowed for a directory.
/// </summary>
/// <param name="provider">The provider that manages the directory.</param>
/// <param name="directory">The full path of the directory.</param>
/// <param name="action">The action the user is attempting to perform.</param>
/// <param name="currentUser">The current user.</param>
/// <param name="groups">The groups the user is member of.</param>
/// <returns><c>true</c> if the action is allowed, <c>false</c> otherwise.</returns>
public static bool CheckActionForDirectory(IFilesStorageProviderV30 provider, string directory, string action, string currentUser, string[] groups) {
if(provider == null) throw new ArgumentNullException("provider");
if(directory == null) throw new ArgumentNullException("directory");
if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory");
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(!AuthTools.IsValidAction(action, Actions.ForDirectories.All)) throw new ArgumentException("Invalid action", "action");
if(currentUser == null) throw new ArgumentNullException("currentUser");
if(currentUser.Length == 0) throw new ArgumentException("Current User cannot be empty", "currentUser");
if(groups == null) throw new ArgumentNullException("groups");
if(currentUser == "admin") return true;
string resourceName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, directory);
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(resourceName);
Authorization auth = AclEvaluator.AuthorizeAction(resourceName, action,
AuthTools.PrepareUsername(currentUser), AuthTools.PrepareGroups(groups), entries);
if(auth != Authorization.Unknown) return auth == Authorization.Granted;
// Try local escalators
string[] localEscalators = null;
if(Actions.ForDirectories.LocalEscalators.TryGetValue(action, out localEscalators)) {
foreach(string localAction in localEscalators) {
bool authorized = CheckActionForDirectory(provider, directory, localAction, currentUser, groups);
if(authorized) return true;
}
}
// Try directory escalation (extract parent directory and check its permissions)
// Path manipulation keeps the format used by the caller (leading and trailing slashes are preserved if appropriate)
string trimmedDirectory = directory.Trim('/');
if(trimmedDirectory.Length > 0) {
int slashIndex = trimmedDirectory.LastIndexOf('/');
string parentDir = "";
if(slashIndex > 0) {
// Navigate one level up, using the same slash format as the current one
parentDir = (directory.StartsWith("/") ? "/" : "") +
trimmedDirectory.Substring(0, slashIndex) + (directory.EndsWith("/") ? "/" : "");
}
else {
// This is the root
parentDir = directory.StartsWith("/") ? "/" : "";
}
bool authorized = CheckActionForDirectory(provider, parentDir, action, currentUser, groups);
if(authorized) return true;
}
// Try global escalators
string[] globalEscalators = null;
if(Actions.ForDirectories.GlobalEscalators.TryGetValue(action, out globalEscalators)) {
foreach(string globalAction in globalEscalators) {
bool authorized = CheckActionForGlobals(globalAction, currentUser, groups);
if(authorized) return true;
}
}
return false;
}
}
}

481
Core/AuthReader.cs Normal file
View file

@ -0,0 +1,481 @@

using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.AclEngine;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Utility class for reading permissions and authorizations.
/// </summary>
public static class AuthReader {
/// <summary>
/// Gets the settings storage provider.
/// </summary>
private static ISettingsStorageProviderV30 SettingsProvider {
get { return Collectors.SettingsProvider; }
}
/// <summary>
/// Gets all the actions for global resources that are granted to a group.
/// </summary>
/// <param name="group">The user group.</param>
/// <returns>The granted actions.</returns>
public static string[] RetrieveGrantsForGlobals(UserGroup group) {
if(group == null) throw new ArgumentNullException("group");
return RetrieveGrantsForGlobals(AuthTools.PrepareGroup(group.Name));
}
/// <summary>
/// Gets all the actions for global resources that are granted to a user.
/// </summary>
/// <param name="user">The user.</param>
/// <returns>The granted actions.</returns>
public static string[] RetrieveGrantsForGlobals(UserInfo user) {
if(user == null) throw new ArgumentNullException("user");
return RetrieveGrantsForGlobals(AuthTools.PrepareUsername(user.Username));
}
/// <summary>
/// Gets all the actions for global resources that are granted to a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <returns>The granted actions.</returns>
private static string[] RetrieveGrantsForGlobals(string subject) {
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject);
List<string> result = new List<string>(entries.Length);
foreach(AclEntry entry in entries) {
if(entry.Value == Value.Grant && entry.Resource == Actions.ForGlobals.ResourceMasterPrefix) {
result.Add(entry.Action);
}
}
return result.ToArray();
}
/// <summary>
/// Gets all the actions for global resources that are denied to a group.
/// </summary>
/// <param name="group">The user group.</param>
/// <returns>The denied actions.</returns>
public static string[] RetrieveDenialsForGlobals(UserGroup group) {
if(group == null) throw new ArgumentNullException("group");
return RetrieveDenialsForGlobals(AuthTools.PrepareGroup(group.Name));
}
/// <summary>
/// Gets all the actions for global resources that are denied to a user.
/// </summary>
/// <param name="user">The user.</param>
/// <returns>The denied actions.</returns>
public static string[] RetrieveDenialsForGlobals(UserInfo user) {
if(user == null) throw new ArgumentNullException("user");
return RetrieveDenialsForGlobals(AuthTools.PrepareUsername(user.Username));
}
/// <summary>
/// Gets all the actions for global resources that are denied to a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <returns>The denied actions.</returns>
private static string[] RetrieveDenialsForGlobals(string subject) {
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject);
List<string> result = new List<string>(entries.Length);
foreach(AclEntry entry in entries) {
if(entry.Value == Value.Deny && entry.Resource == Actions.ForGlobals.ResourceMasterPrefix) {
result.Add(entry.Action);
}
}
return result.ToArray();
}
/// <summary>
/// Retrieves the subjects that have ACL entries set for a namespace.
/// </summary>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns>The subjects.</returns>
public static SubjectInfo[] RetrieveSubjectsForNamespace(NamespaceInfo nspace) {
string resourceName = Actions.ForNamespaces.ResourceMasterPrefix;
if(nspace != null) resourceName += nspace.Name;
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(resourceName);
List<SubjectInfo> result = new List<SubjectInfo>(entries.Length);
for(int i = 0; i < entries.Length; i++) {
SubjectType type = AuthTools.IsGroup(entries[i].Subject) ? SubjectType.Group : SubjectType.User;
// Remove the subject qualifier ('U.' or 'G.')
string name = entries[i].Subject.Substring(2);
if(result.Find(delegate(SubjectInfo x) { return x.Name == name && x.Type == type; }) == null) {
result.Add(new SubjectInfo(name, type));
}
}
return result.ToArray();
}
/// <summary>
/// Gets all the actions for a namespace that are granted to a group.
/// </summary>
/// <param name="group">The user group.</param>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns>The granted actions.</returns>
public static string[] RetrieveGrantsForNamespace(UserGroup group, NamespaceInfo nspace) {
if(group == null) throw new ArgumentNullException("group");
return RetrieveGrantsForNamespace(AuthTools.PrepareGroup(group.Name), nspace);
}
/// <summary>
/// Gets all the actions for a namespace that are granted to a user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns>The granted actions.</returns>
public static string[] RetrieveGrantsForNamespace(UserInfo user, NamespaceInfo nspace) {
if(user == null) throw new ArgumentNullException("user");
return RetrieveGrantsForNamespace(AuthTools.PrepareUsername(user.Username), nspace);
}
/// <summary>
/// Gets all the actions for a namespace that are granted to a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns>The granted actions.</returns>
private static string[] RetrieveGrantsForNamespace(string subject, NamespaceInfo nspace) {
string resourceName = Actions.ForNamespaces.ResourceMasterPrefix;
if(nspace != null) resourceName += nspace.Name;
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject);
List<string> result = new List<string>(entries.Length);
foreach(AclEntry entry in entries) {
if(entry.Value == Value.Grant && entry.Resource == resourceName) {
result.Add(entry.Action);
}
}
return result.ToArray();
}
/// <summary>
/// Gets all the actions for a namespace that are denied to a group.
/// </summary>
/// <param name="group">The user group.</param>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns>The denied actions.</returns>
public static string[] RetrieveDenialsForNamespace(UserGroup group, NamespaceInfo nspace) {
if(group == null) throw new ArgumentNullException("group");
return RetrieveDenialsForNamespace(AuthTools.PrepareGroup(group.Name), nspace);
}
/// <summary>
/// Gets all the actions for a namespace that are denied to a user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns>The denied actions.</returns>
public static string[] RetrieveDenialsForNamespace(UserInfo user, NamespaceInfo nspace) {
if(user == null) throw new ArgumentNullException("user");
return RetrieveDenialsForNamespace(AuthTools.PrepareUsername(user.Username), nspace);
}
/// <summary>
/// Gets all the actions for a namespace that are denied to a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns>The denied actions.</returns>
private static string[] RetrieveDenialsForNamespace(string subject, NamespaceInfo nspace) {
string resourceName = Actions.ForNamespaces.ResourceMasterPrefix;
if(nspace != null) resourceName += nspace.Name;
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject);
List<string> result = new List<string>(entries.Length);
foreach(AclEntry entry in entries) {
if(entry.Value == Value.Deny && entry.Resource == resourceName) {
result.Add(entry.Action);
}
}
return result.ToArray();
}
/// <summary>
/// Retrieves the subjects that have ACL entries set for a page.
/// </summary>
/// <param name="page">The page.</param>
/// <returns>The subjects.</returns>
public static SubjectInfo[] RetrieveSubjectsForPage(PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(Actions.ForPages.ResourceMasterPrefix + page.FullName);
List<SubjectInfo> result = new List<SubjectInfo>(entries.Length);
for(int i = 0; i < entries.Length; i++) {
SubjectType type = AuthTools.IsGroup(entries[i].Subject) ? SubjectType.Group : SubjectType.User;
// Remove the subject qualifier ('U.' or 'G.')
string name = entries[i].Subject.Substring(2);
if(result.Find(delegate(SubjectInfo x) { return x.Name == name && x.Type == type; }) == null) {
result.Add(new SubjectInfo(name, type));
}
}
return result.ToArray();
}
/// <summary>
/// Gets all the actions for a page that are granted to a group.
/// </summary>
/// <param name="group">The user group.</param>
/// <param name="page">The page.</param>
/// <returns>The granted actions.</returns>
public static string[] RetrieveGrantsForPage(UserGroup group, PageInfo page) {
if(group == null) throw new ArgumentNullException("group");
return RetrieveGrantsForPage(AuthTools.PrepareGroup(group.Name), page);
}
/// <summary>
/// Gets all the actions for a page that are granted to a user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="page">The page.</param>
/// <returns>The granted actions.</returns>
public static string[] RetrieveGrantsForPage(UserInfo user, PageInfo page) {
if(user == null) throw new ArgumentNullException("user");
return RetrieveGrantsForPage(AuthTools.PrepareUsername(user.Username), page);
}
/// <summary>
/// Gets all the actions for a page that are granted to a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <param name="page">The page.</param>
/// <returns>The granted actions.</returns>
private static string[] RetrieveGrantsForPage(string subject, PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
string resourceName = Actions.ForPages.ResourceMasterPrefix + page.FullName;
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject);
List<string> result = new List<string>(entries.Length);
foreach(AclEntry entry in entries) {
if(entry.Value == Value.Grant && entry.Resource == resourceName) {
result.Add(entry.Action);
}
}
return result.ToArray();
}
/// <summary>
/// Gets all the actions for a page that are denied to a group.
/// </summary>
/// <param name="group">The user group.</param>
/// <param name="page">The page.</param>
/// <returns>The granted actions.</returns>
public static string[] RetrieveDenialsForPage(UserGroup group, PageInfo page) {
if(group == null) throw new ArgumentNullException("group");
return RetrieveDenialsForPage(AuthTools.PrepareGroup(group.Name), page);
}
/// <summary>
/// Gets all the actions for a page that are denied to a user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="page">The page.</param>
/// <returns>The granted actions.</returns>
public static string[] RetrieveDenialsForPage(UserInfo user, PageInfo page) {
if(user == null) throw new ArgumentNullException("user");
return RetrieveDenialsForPage(AuthTools.PrepareUsername(user.Username), page);
}
/// <summary>
/// Gets all the actions for a page that are denied to a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <param name="page">The page.</param>
/// <returns>The granted actions.</returns>
private static string[] RetrieveDenialsForPage(string subject, PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
string resourceName = Actions.ForPages.ResourceMasterPrefix + page.FullName;
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject);
List<string> result = new List<string>(entries.Length);
foreach(AclEntry entry in entries) {
if(entry.Value == Value.Deny && entry.Resource == resourceName) {
result.Add(entry.Action);
}
}
return result.ToArray();
}
/// <summary>
/// Retrieves the subjects that have ACL entries set for a directory.
/// </summary>
/// <param name="provider">The provider.</param>
/// <param name="directory">The directory.</param>
/// <returns>The subjects.</returns>
public static SubjectInfo[] RetrieveSubjectsForDirectory(IFilesStorageProviderV30 provider, string directory) {
if(provider == null) throw new ArgumentNullException("provider");
if(directory == null) throw new ArgumentNullException("directory");
if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory");
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, directory));
List<SubjectInfo> result = new List<SubjectInfo>(entries.Length);
for(int i = 0; i < entries.Length; i++) {
SubjectType type = AuthTools.IsGroup(entries[i].Subject) ? SubjectType.Group : SubjectType.User;
// Remove the subject qualifier ('U.' or 'G.')
string name = entries[i].Subject.Substring(2);
if(result.Find(delegate(SubjectInfo x) { return x.Name == name && x.Type == type; }) == null) {
result.Add(new SubjectInfo(name, type));
}
}
return result.ToArray();
}
/// <summary>
/// Gets all the actions for a directory that are granted to a group.
/// </summary>
/// <param name="group">The user group.</param>
/// <param name="provider">The provider.</param>
/// <param name="directory">The directory.</param>
/// <returns>The granted actions.</returns>
public static string[] RetrieveGrantsForDirectory(UserGroup group, IFilesStorageProviderV30 provider, string directory) {
if(group == null) throw new ArgumentNullException("group");
return RetrieveGrantsForDirectory(AuthTools.PrepareGroup(group.Name), provider, directory);
}
/// <summary>
/// Gets all the actions for a directory that are granted to a user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="provider">The provider.</param>
/// <param name="directory">The directory.</param>
/// <returns>The granted actions.</returns>
public static string[] RetrieveGrantsForDirectory(UserInfo user, IFilesStorageProviderV30 provider, string directory) {
if(user == null) throw new ArgumentNullException("user");
return RetrieveGrantsForDirectory(AuthTools.PrepareUsername(user.Username), provider, directory);
}
/// <summary>
/// Gets all the actions for a directory that are granted to a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <param name="provider">The provider.</param>
/// <param name="directory">The directory.</param>
/// <returns>The granted actions.</returns>
private static string[] RetrieveGrantsForDirectory(string subject, IFilesStorageProviderV30 provider, string directory) {
if(provider == null) throw new ArgumentNullException("provider");
if(directory == null) throw new ArgumentNullException("directory");
if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory");
string resourceName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, directory);
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject);
List<string> result = new List<string>(entries.Length);
foreach(AclEntry entry in entries) {
if(entry.Value == Value.Grant && entry.Resource == resourceName) {
result.Add(entry.Action);
}
}
return result.ToArray();
}
/// <summary>
/// Gets all the actions for a directory that are denied to a group.
/// </summary>
/// <param name="group">The user group.</param>
/// <param name="provider">The provider.</param>
/// <param name="directory">The directory.</param>
/// <returns>The denied actions.</returns>
public static string[] RetrieveDenialsForDirectory(UserGroup group, IFilesStorageProviderV30 provider, string directory) {
if(group == null) throw new ArgumentNullException("group");
return RetrieveDenialsForDirectory(AuthTools.PrepareGroup(group.Name), provider, directory);
}
/// <summary>
/// Gets all the actions for a directory that are denied to a user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="provider">The provider.</param>
/// <param name="directory">The directory.</param>
/// <returns>The denied actions.</returns>
public static string[] RetrieveDenialsForDirectory(UserInfo user, IFilesStorageProviderV30 provider, string directory) {
if(user == null) throw new ArgumentNullException("user");
return RetrieveDenialsForDirectory(AuthTools.PrepareUsername(user.Username), provider, directory);
}
/// <summary>
/// Gets all the actions for a directory that are denied to a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <param name="provider">The provider.</param>
/// <param name="directory">The directory.</param>
/// <returns>The denied actions.</returns>
private static string[] RetrieveDenialsForDirectory(string subject, IFilesStorageProviderV30 provider, string directory) {
if(provider == null) throw new ArgumentNullException("provider");
if(directory == null) throw new ArgumentNullException("directory");
if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory");
string resourceName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, directory);
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject);
List<string> result = new List<string>(entries.Length);
foreach(AclEntry entry in entries) {
if(entry.Value == Value.Deny && entry.Resource == resourceName) {
result.Add(entry.Action);
}
}
return result.ToArray();
}
}
}

26
Core/AuthStatus.cs Normal file
View file

@ -0,0 +1,26 @@

using System;
using System.Collections.Generic;
using System.Text;
namespace ScrewTurn.Wiki {
/// <summary>
/// Lists legal values for authorizations.
/// </summary>
public enum AuthStatus {
/// <summary>
/// Grant the action.
/// </summary>
Grant,
/// <summary>
/// Deny the action.
/// </summary>
Deny,
/// <summary>
/// Delete the permission entry.
/// </summary>
Delete
}
}

95
Core/AuthTools.cs Normal file
View file

@ -0,0 +1,95 @@

using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Implements tools supporting athorization management.
/// </summary>
public static class AuthTools {
/// <summary>
/// Determines whether an action is valid.
/// </summary>
/// <param name="action">The action to validate.</param>
/// <param name="validActions">The list of valid actions.</param>
/// <returns><c>true</c> if the action is valid, <c>false</c> otherwise.</returns>
public static bool IsValidAction(string action, string[] validActions) {
return Array.Find(validActions, delegate(string s) { return s == action; }) != null;
}
/// <summary>
/// Determines whether a subject is a group.
/// </summary>
/// <param name="subject">The subject to test.</param>
/// <returns><c>true</c> if the subject is a group, <c>false</c> if it is a user.</returns>
public static bool IsGroup(string subject) {
if(subject == null) throw new ArgumentNullException("subject");
if(subject.Length < 2) throw new ArgumentException("Subject must contain at least 2 characters", "subject");
return subject.ToUpperInvariant().StartsWith("G.");
}
/// <summary>
/// Prepends the proper string to a username.
/// </summary>
/// <param name="username">The username.</param>
/// <returns>The resulting username.</returns>
public static string PrepareUsername(string username) {
if(username == null) throw new ArgumentNullException("username");
if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username");
return "U." + username;
}
/// <summary>
/// Prepends the proper string to each group name in an array.
/// </summary>
/// <param name="groups">The group array.</param>
/// <returns>The resulting group array.</returns>
public static string[] PrepareGroups(string[] groups) {
if(groups == null) throw new ArgumentNullException("groups");
if(groups.Length == 0) return groups;
string[] result = new string[groups.Length];
for(int i = 0; i < groups.Length; i++) {
if(groups[i] == null) throw new ArgumentNullException("groups");
if(groups[i].Length == 0) throw new ArgumentException("Groups cannot contain empty elements", "groups");
result[i] = PrepareGroup(groups[i]);
}
return result;
}
/// <summary>
/// Prepends the proper string to the group name.
/// </summary>
/// <param name="group">The group name.</param>
/// <returns>The result string.</returns>
public static string PrepareGroup(string group) {
return "G." + group;
}
/// <summary>
/// Gets the proper full name for a directory.
/// </summary>
/// <param name="prov">The provider.</param>
/// <param name="name">The directory name.</param>
/// <returns>The full name (<b>not</b> prepended with <see cref="Actions.ForDirectories.ResourceMasterPrefix" />.</returns>
public static string GetDirectoryName(IFilesStorageProviderV30 prov, string name) {
if(prov == null) throw new ArgumentNullException("prov");
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
return "(" + prov.GetType().FullName + ")" + name;
}
}
}

668
Core/AuthWriter.cs Normal file
View file

@ -0,0 +1,668 @@

using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using ScrewTurn.Wiki.AclEngine;
namespace ScrewTurn.Wiki {
/// <summary>
/// Utility class for writing permissions and authorizations.
/// </summary>
public static class AuthWriter {
private const string Delete = "DELETE";
private const string Set = "SET-";
private const string MessageDeleteSuccess = "Deleted ACL Entry: ";
private const string MessageDeleteFailure = "Deletion failed for ACL Entry: ";
private const string MessageSetSuccess = "Set ACL Entry: ";
private const string MessageSetFailure = "Setting failed for ACL Entry: ";
/// <summary>
/// Gets the settings storage provider.
/// </summary>
private static ISettingsStorageProviderV30 SettingsProvider {
get { return Collectors.SettingsProvider; }
}
/// <summary>
/// Sets a permission for a global resource.
/// </summary>
/// <param name="status">The authorization status.</param>
/// <param name="action">The action of which to modify the authorization status.</param>
/// <param name="group">The group subject of the authorization change.</param>
/// <returns><c>true</c> if the authorization status is changed, <c>false</c> otherwise.</returns>
public static bool SetPermissionForGlobals(AuthStatus status, string action, UserGroup group) {
if(group == null) throw new ArgumentNullException("group");
return SetPermissionForGlobals(status, action, AuthTools.PrepareGroup(group.Name));
}
/// <summary>
/// Sets a permission for a global resource.
/// </summary>
/// <param name="status">The authorization status.</param>
/// <param name="action">The action of which to modify the authorization status.</param>
/// <param name="user">The user subject of the authorization change.</param>
/// <returns><c>true</c> if the authorization status is changed, <c>false</c> otherwise.</returns>
public static bool SetPermissionForGlobals(AuthStatus status, string action, UserInfo user) {
if(user == null) throw new ArgumentNullException("user");
return SetPermissionForGlobals(status, action, AuthTools.PrepareUsername(user.Username));
}
/// <summary>
/// Sets a permission for a global resource.
/// </summary>
/// <param name="status">The authorization status.</param>
/// <param name="action">The action of which to modify the authorization status.</param>
/// <param name="subject">The subject of the authorization change.</param>
/// <returns><c>true</c> if the authorization status is changed, <c>false</c> otherwise.</returns>
private static bool SetPermissionForGlobals(AuthStatus status, string action, string subject) {
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(action != Actions.FullControl && !AuthTools.IsValidAction(action, Actions.ForGlobals.All)) {
throw new ArgumentException("Invalid action", "action");
}
if(status == AuthStatus.Delete) {
bool done = SettingsProvider.AclManager.DeleteEntry(Actions.ForGlobals.ResourceMasterPrefix, action, subject);
if(done) {
Log.LogEntry(MessageDeleteSuccess + GetLogMessage(Actions.ForGlobals.ResourceMasterPrefix, "",
action, subject, Delete), EntryType.General, Log.SystemUsername);
}
else {
Log.LogEntry(MessageDeleteFailure + GetLogMessage(Actions.ForGlobals.ResourceMasterPrefix, "",
action, subject, Delete), EntryType.Error, Log.SystemUsername);
}
return done;
}
else {
bool done = SettingsProvider.AclManager.StoreEntry(Actions.ForGlobals.ResourceMasterPrefix,
action, subject, status == AuthStatus.Grant ? Value.Grant : Value.Deny);
if(done) {
Log.LogEntry(MessageSetSuccess + GetLogMessage(Actions.ForGlobals.ResourceMasterPrefix, "",
action, subject, Set + status.ToString()), EntryType.General, Log.SystemUsername);
}
else {
Log.LogEntry(MessageSetFailure + GetLogMessage(Actions.ForGlobals.ResourceMasterPrefix, "",
action, subject, Set + status.ToString()), EntryType.Error, Log.SystemUsername);
}
return done;
}
}
/// <summary>
/// Sets a permission for a namespace.
/// </summary>
/// <param name="status">The authorization status.</param>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <param name="action">The action of which to modify the authorization status.</param>
/// <param name="group">The group subject of the authorization change.</param>
/// <returns><c>true</c> if the authorization status is changed, <c>false</c> otherwise.</returns>
public static bool SetPermissionForNamespace(AuthStatus status, NamespaceInfo nspace, string action, UserGroup group) {
if(group == null) throw new ArgumentNullException("group");
return SetPermissionForNamespace(status, nspace, action, AuthTools.PrepareGroup(group.Name));
}
/// <summary>
/// Sets a permission for a namespace.
/// </summary>
/// <param name="status">The authorization status.</param>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <param name="action">The action of which to modify the authorization status.</param>
/// <param name="user">The user subject of the authorization change.</param>
/// <returns><c>true</c> if the authorization status is changed, <c>false</c> otherwise.</returns>
public static bool SetPermissionForNamespace(AuthStatus status, NamespaceInfo nspace, string action, UserInfo user) {
if(user == null) throw new ArgumentNullException("user");
return SetPermissionForNamespace(status, nspace, action, AuthTools.PrepareUsername(user.Username));
}
/// <summary>
/// Sets a permission for a namespace.
/// </summary>
/// <param name="status">The authorization status.</param>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <param name="action">The action of which to modify the authorization status.</param>
/// <param name="subject">The subject of the authorization change.</param>
/// <returns><c>true</c> if the authorization status is changed, <c>false</c> otherwise.</returns>
private static bool SetPermissionForNamespace(AuthStatus status, NamespaceInfo nspace, string action, string subject) {
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(action != Actions.FullControl && !AuthTools.IsValidAction(action, Actions.ForNamespaces.All)) {
throw new ArgumentException("Invalid action", "action");
}
string namespaceName = nspace != null ? nspace.Name : "";
if(status == AuthStatus.Delete) {
bool done = SettingsProvider.AclManager.DeleteEntry(Actions.ForNamespaces.ResourceMasterPrefix + namespaceName,
action, subject);
if(done) {
Log.LogEntry(MessageDeleteSuccess + GetLogMessage(Actions.ForNamespaces.ResourceMasterPrefix, namespaceName,
action, subject, Delete), EntryType.General, Log.SystemUsername);
}
else {
Log.LogEntry(MessageDeleteFailure + GetLogMessage(Actions.ForNamespaces.ResourceMasterPrefix, namespaceName,
action, subject, Delete), EntryType.Error, Log.SystemUsername);
}
return done;
}
else {
bool done = SettingsProvider.AclManager.StoreEntry(Actions.ForNamespaces.ResourceMasterPrefix + namespaceName,
action, subject, status == AuthStatus.Grant ? Value.Grant : Value.Deny);
if(done) {
Log.LogEntry(MessageSetSuccess + GetLogMessage(Actions.ForNamespaces.ResourceMasterPrefix, namespaceName,
action, subject, Set + status.ToString()), EntryType.General, Log.SystemUsername);
}
else {
Log.LogEntry(MessageSetFailure + GetLogMessage(Actions.ForNamespaces.ResourceMasterPrefix, namespaceName,
action, subject, Set + status.ToString()), EntryType.Error, Log.SystemUsername);
}
return done;
}
}
/// <summary>
/// Sets a permission for a directory.
/// </summary>
/// <param name="status">The authorization status.</param>
/// <param name="provider">The provider that handles the directory.</param>
/// <param name="directory">The directory.</param>
/// <param name="action">The action of which to modify the authorization status.</param>
/// <param name="group">The group subject of the authorization change.</param>
/// <returns><c>true</c> if the authorization status is changed, <c>false</c> otherwise.</returns>
public static bool SetPermissionForDirectory(AuthStatus status, IFilesStorageProviderV30 provider, string directory, string action, UserGroup group) {
if(group == null) throw new ArgumentNullException("group");
return SetPermissionForDirectory(status, provider, directory, action, AuthTools.PrepareGroup(group.Name));
}
/// <summary>
/// Sets a permission for a directory.
/// </summary>
/// <param name="status">The authorization status.</param>
/// <param name="provider">The provider that handles the directory.</param>
/// <param name="directory">The directory.</param>
/// <param name="action">The action of which to modify the authorization status.</param>
/// <param name="user">The user subject of the authorization change.</param>
/// <returns><c>true</c> if the authorization status is changed, <c>false</c> otherwise.</returns>
public static bool SetPermissionForDirectory(AuthStatus status, IFilesStorageProviderV30 provider, string directory, string action, UserInfo user) {
if(user == null) throw new ArgumentNullException("user");
return SetPermissionForDirectory(status, provider, directory, action, AuthTools.PrepareUsername(user.Username));
}
/// <summary>
/// Sets a permission for a directory.
/// </summary>
/// <param name="status">The authorization status.</param>
/// <param name="provider">The provider that handles the directory.</param>
/// <param name="directory">The directory.</param>
/// <param name="action">The action of which to modify the authorization status.</param>
/// <param name="subject">The subject of the authorization change.</param>
/// <returns><c>true</c> if the authorization status is changed, <c>false</c> otherwise.</returns>
private static bool SetPermissionForDirectory(AuthStatus status, IFilesStorageProviderV30 provider, string directory, string action, string subject) {
if(provider == null) throw new ArgumentNullException("provider");
if(directory == null) throw new ArgumentNullException("directory");
if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory");
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(action != Actions.FullControl && !AuthTools.IsValidAction(action, Actions.ForDirectories.All)) {
throw new ArgumentException("Invalid action", "action");
}
string directoryName = AuthTools.GetDirectoryName(provider, directory);
if(status == AuthStatus.Delete) {
bool done = SettingsProvider.AclManager.DeleteEntry(Actions.ForDirectories.ResourceMasterPrefix + directoryName,
action, subject);
if(done) {
Log.LogEntry(MessageDeleteSuccess + GetLogMessage(Actions.ForDirectories.ResourceMasterPrefix, directoryName,
action, subject, Delete), EntryType.General, Log.SystemUsername);
}
else {
Log.LogEntry(MessageDeleteFailure + GetLogMessage(Actions.ForDirectories.ResourceMasterPrefix, directoryName,
action, subject, Delete), EntryType.Error, Log.SystemUsername);
}
return done;
}
else {
bool done = SettingsProvider.AclManager.StoreEntry(Actions.ForDirectories.ResourceMasterPrefix + directoryName,
action, subject, status == AuthStatus.Grant ? Value.Grant : Value.Deny);
if(done) {
Log.LogEntry(MessageSetSuccess + GetLogMessage(Actions.ForDirectories.ResourceMasterPrefix, directoryName,
action, subject, Set + status.ToString()), EntryType.General, Log.SystemUsername);
}
else {
Log.LogEntry(MessageSetFailure + GetLogMessage(Actions.ForDirectories.ResourceMasterPrefix, directoryName,
action, subject, Set + status.ToString()), EntryType.Error, Log.SystemUsername);
}
return done;
}
}
/// <summary>
/// Sets a permission for a page.
/// </summary>
/// <param name="status">The authorization status.</param>
/// <param name="page">The page.</param>
/// <param name="action">The action of which to modify the authorization status.</param>
/// <param name="group">The group subject of the authorization change.</param>
/// <returns><c>true</c> if the authorization status is changed, <c>false</c> otherwise.</returns>
public static bool SetPermissionForPage(AuthStatus status, PageInfo page, string action, UserGroup group) {
if(group == null) throw new ArgumentNullException("group");
return SetPermissionForPage(status, page, action, AuthTools.PrepareGroup(group.Name));
}
/// <summary>
/// Sets a permission for a page.
/// </summary>
/// <param name="status">The authorization status.</param>
/// <param name="page">The page.</param>
/// <param name="action">The action of which to modify the authorization status.</param>
/// <param name="user">The user subject of the authorization change.</param>
/// <returns><c>true</c> if the authorization status is changed, <c>false</c> otherwise.</returns>
public static bool SetPermissionForPage(AuthStatus status, PageInfo page, string action, UserInfo user) {
if(user == null) throw new ArgumentNullException("user");
return SetPermissionForPage(status, page, action, AuthTools.PrepareUsername(user.Username));
}
/// <summary>
/// Sets a permission for a page.
/// </summary>
/// <param name="status">The authorization status.</param>
/// <param name="page">The page.</param>
/// <param name="action">The action of which to modify the authorization status.</param>
/// <param name="subject">The subject of the authorization change.</param>
/// <returns><c>true</c> if the authorization status is changed, <c>false</c> otherwise.</returns>
private static bool SetPermissionForPage(AuthStatus status, PageInfo page, string action, string subject) {
if(page == null) throw new ArgumentNullException("page");
if(action == null) throw new ArgumentNullException("action");
if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action");
if(action != Actions.FullControl && !AuthTools.IsValidAction(action, Actions.ForPages.All)) {
throw new ArgumentException("Invalid action", "action");
}
if(status == AuthStatus.Delete) {
bool done = SettingsProvider.AclManager.DeleteEntry(Actions.ForPages.ResourceMasterPrefix + page.FullName,
action, subject);
if(done) {
Log.LogEntry(MessageDeleteSuccess + GetLogMessage(Actions.ForPages.ResourceMasterPrefix, page.FullName,
action, subject, Delete), EntryType.General, Log.SystemUsername);
}
else {
Log.LogEntry(MessageDeleteFailure + GetLogMessage(Actions.ForPages.ResourceMasterPrefix, page.FullName,
action, subject, Delete), EntryType.Error, Log.SystemUsername);
}
return done;
}
else {
bool done = SettingsProvider.AclManager.StoreEntry(Actions.ForPages.ResourceMasterPrefix + page.FullName,
action, subject, status == AuthStatus.Grant ? Value.Grant : Value.Deny);
if(done) {
Log.LogEntry(MessageSetSuccess + GetLogMessage(Actions.ForPages.ResourceMasterPrefix, page.FullName,
action, subject, Set + status.ToString()), EntryType.General, Log.SystemUsername);
}
else {
Log.LogEntry(MessageSetFailure + GetLogMessage(Actions.ForPages.ResourceMasterPrefix, page.FullName,
action, subject, Set + status.ToString()), EntryType.Error, Log.SystemUsername);
}
return done;
}
}
/// <summary>
/// Removes all the ACL Entries for global resources that are bound to a user group.
/// </summary>
/// <param name="group">The user group.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
public static bool RemoveEntriesForGlobals(UserGroup group) {
if(group == null) throw new ArgumentNullException("group");
return RemoveEntriesForGlobals(AuthTools.PrepareGroup(group.Name));
}
/// <summary>
/// Removes all the ACL Entries for global resources that are bound to a user.
/// </summary>
/// <param name="user">The user.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
public static bool RemoveEntriesForGlobals(UserInfo user) {
if(user == null) throw new ArgumentNullException("user");
return RemoveEntriesForGlobals(AuthTools.PrepareUsername(user.Username));
}
/// <summary>
/// Removes all the ACL Entries for global resources that are bound to a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
private static bool RemoveEntriesForGlobals(string subject) {
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject);
foreach(AclEntry entry in entries) {
if(entry.Resource == Actions.ForGlobals.ResourceMasterPrefix) {
// This call automatically logs the operation result
bool done = SetPermissionForGlobals(AuthStatus.Delete, entry.Action, subject);
if(!done) return false;
}
}
return true;
}
/// <summary>
/// Removes all the ACL Entries for a namespace that are bound to a user group.
/// </summary>
/// <param name="group">The user group.</param>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
public static bool RemoveEntriesForNamespace(UserGroup group, NamespaceInfo nspace) {
if(group == null) throw new ArgumentNullException("group");
return RemoveEntriesForNamespace(AuthTools.PrepareGroup(group.Name), nspace);
}
/// <summary>
/// Removes all the ACL Entries for a namespace that are bound to a user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
public static bool RemoveEntriesForNamespace(UserInfo user, NamespaceInfo nspace) {
if(user == null) throw new ArgumentNullException("user");
return RemoveEntriesForNamespace(AuthTools.PrepareUsername(user.Username), nspace);
}
/// <summary>
/// Removes all the ACL Entries for a namespace that are bound to a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
private static bool RemoveEntriesForNamespace(string subject, NamespaceInfo nspace) {
string resourceName = Actions.ForNamespaces.ResourceMasterPrefix;
if(nspace != null) resourceName += nspace.Name;
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject);
foreach(AclEntry entry in entries) {
if(entry.Resource == resourceName) {
// This call automatically logs the operation result
bool done = SetPermissionForNamespace(AuthStatus.Delete, nspace, entry.Action, subject);
if(!done) return false;
}
}
return true;
}
/// <summary>
/// Removes all the ACL Entries for a page that are bound to a user group.
/// </summary>
/// <param name="group">The user group.</param>
/// <param name="page">The page.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
public static bool RemoveEntriesForPage(UserGroup group, PageInfo page) {
if(group == null) throw new ArgumentNullException("group");
return RemoveEntriesForPage(AuthTools.PrepareGroup(group.Name), page);
}
/// <summary>
/// Removes all the ACL Entries for a page that are bound to a user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="page">The page.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
public static bool RemoveEntriesForPage(UserInfo user, PageInfo page) {
if(user == null) throw new ArgumentNullException("user");
return RemoveEntriesForPage(AuthTools.PrepareUsername(user.Username), page);
}
/// <summary>
/// Removes all the ACL Entries for a page that are bound to a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <param name="page">The page.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
private static bool RemoveEntriesForPage(string subject, PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
string resourceName = Actions.ForPages.ResourceMasterPrefix + page.FullName;
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject);
foreach(AclEntry entry in entries) {
if(entry.Resource == resourceName) {
// This call automatically logs the operation result
bool done = SetPermissionForPage(AuthStatus.Delete, page, entry.Action, subject);
if(!done) return false;
}
}
return true;
}
/// <summary>
/// Removes all the ACL Entries for a directory that are bound to a user group.
/// </summary>
/// <param name="group">The group.</param>
/// <param name="provider">The provider.</param>
/// <param name="directory">The directory.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
public static bool RemoveEntriesForDirectory(UserGroup group, IFilesStorageProviderV30 provider, string directory) {
if(group == null) throw new ArgumentNullException("group");
return RemoveEntriesForDirectory(AuthTools.PrepareGroup(group.Name), provider, directory);
}
/// <summary>
/// Removes all the ACL Entries for a directory that are bound to a user.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="provider">The provider.</param>
/// <param name="directory">The directory.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
public static bool RemoveEntriesForDirectory(UserInfo user, IFilesStorageProviderV30 provider, string directory) {
if(user == null) throw new ArgumentNullException("user");
return RemoveEntriesForDirectory(AuthTools.PrepareUsername(user.Username), provider, directory);
}
/// <summary>
/// Removes all the ACL Entries for a directory that are bound to a subject.
/// </summary>
/// <param name="subject">The subject.</param>
/// <param name="provider">The provider.</param>
/// <param name="directory">The directory.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
private static bool RemoveEntriesForDirectory(string subject, IFilesStorageProviderV30 provider, string directory) {
if(provider == null) throw new ArgumentNullException("provider");
if(directory == null) throw new ArgumentNullException("directory");
if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory");
string resourceName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, directory);
AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject);
foreach(AclEntry entry in entries) {
if(entry.Resource == resourceName) {
// This call automatically logs the operation result
bool done = SetPermissionForDirectory(AuthStatus.Delete, provider, directory, entry.Action, subject);
if(!done) return false;
}
}
return true;
}
/// <summary>
/// Clears all the ACL entries for a directory.
/// </summary>
/// <param name="provider">The provider.</param>
/// <param name="directory">The directory.</param>
public static void ClearEntriesForDirectory(IFilesStorageProviderV30 provider, string directory) {
if(provider == null) throw new ArgumentNullException("provider");
if(directory == null) throw new ArgumentNullException("directory");
if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory");
string resourceName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, directory);
SettingsProvider.AclManager.DeleteEntriesForResource(resourceName);
}
/// <summary>
/// Clears all the ACL entries for a namespace.
/// </summary>
/// <param name="nspace">The namespace.</param>
/// <param name="pages">The local names of the pages in the namespace.</param>
public static void ClearEntriesForNamespace(string nspace, List<string> pages) {
if(nspace == null) throw new ArgumentNullException("nspac");
if(nspace.Length == 0) throw new ArgumentException("Namespace cannot be empty", "nspace");
if(pages == null) throw new ArgumentNullException("pages");
foreach(string p in pages) {
if(p == null) throw new ArgumentNullException("pages");
if(p.Length == 0) throw new ArgumentException("Page Element cannot be empty", "pages");
}
string resourceName;
foreach(string p in pages) {
resourceName = Actions.ForPages.ResourceMasterPrefix + NameTools.GetFullName(nspace, p);
SettingsProvider.AclManager.DeleteEntriesForResource(resourceName);
}
resourceName = Actions.ForNamespaces.ResourceMasterPrefix + nspace;
SettingsProvider.AclManager.DeleteEntriesForResource(resourceName);
}
/// <summary>
/// Clears all the ACL entries for a page.
/// </summary>
/// <param name="page">The page full name.</param>
public static void ClearEntriesForPage(string page) {
if(page == null) throw new ArgumentNullException("page");
if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page");
string resourceName = Actions.ForPages.ResourceMasterPrefix + page;
SettingsProvider.AclManager.DeleteEntriesForResource(resourceName);
}
/// <summary>
/// Processes the renaming of a directory.
/// </summary>
/// <param name="provider">The provider.</param>
/// <param name="oldName">The old directory name (full path).</param>
/// <param name="newName">The new directory name (full path).</param>
/// <returns><c>true</c> if the operation completed successfully, <c>false</c> otherwise.</returns>
/// <remarks>The method <b>does not</b> recurse in sub-directories.</remarks>
public static bool ProcessDirectoryRenaming(IFilesStorageProviderV30 provider, string oldName, string newName) {
if(provider == null) throw new ArgumentNullException("provider");
if(oldName == null) throw new ArgumentNullException("oldName");
if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName");
if(newName == null) throw new ArgumentNullException("newName");
if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");
return SettingsProvider.AclManager.RenameResource(
Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, oldName),
Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, newName));
}
/// <summary>
/// Processes the renaming of a namespace.
/// </summary>
/// <param name="oldName">The old name of the namespace.</param>
/// <param name="oldPages">The list of local names of the pages in the renamed namespace.</param>
/// <param name="newName">The new name of the namespace.</param>
/// <returns><c>true</c> if the operation completed successfully, <c>false</c> otherwise.</returns>
public static bool ProcessNamespaceRenaming(string oldName, List<string> oldPages, string newName) {
if(oldName == null) throw new ArgumentNullException("oldName");
if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName");
if(oldPages == null) throw new ArgumentNullException("oldPages");
foreach(string p in oldPages) {
if(p == null) throw new ArgumentNullException("oldPages");
if(p.Length == 0) throw new ArgumentException("Page cannot be empty", "oldPages");
}
if(newName == null) throw new ArgumentNullException("newName");
if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");
foreach(string p in oldPages) {
SettingsProvider.AclManager.RenameResource(
Actions.ForPages.ResourceMasterPrefix + NameTools.GetFullName(oldName, p),
Actions.ForPages.ResourceMasterPrefix + NameTools.GetFullName(newName, p));
}
return SettingsProvider.AclManager.RenameResource(
Actions.ForNamespaces.ResourceMasterPrefix + oldName,
Actions.ForNamespaces.ResourceMasterPrefix + newName);
}
/// <summary>
/// Processes the renaming of a page.
/// </summary>
/// <param name="oldName">The old full page name.</param>
/// <param name="newName">The new full page name.</param>
/// <returns><c>true</c> if the operation completed successfully, <c>false</c> otherwise.</returns>
public static bool ProcessPageRenaming(string oldName, string newName) {
if(oldName == null) throw new ArgumentNullException("oldName");
if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName");
if(newName == null) throw new ArgumentNullException("newName");
if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");
return SettingsProvider.AclManager.RenameResource(
Actions.ForPages.ResourceMasterPrefix + oldName,
Actions.ForPages.ResourceMasterPrefix + newName);
}
/// <summary>
/// Gets the log message for an ACL entry change.
/// </summary>
/// <param name="resourcePrefix">The resource prefix.</param>
/// <param name="resource">The resource name.</param>
/// <param name="action">The action.</param>
/// <param name="subject">The subject.</param>
/// <param name="status">The status.</param>
/// <returns>The message.</returns>
private static string GetLogMessage(string resourcePrefix, string resource, string action, string subject, string status) {
return resourcePrefix + resource + ":" + action + ":" + subject + "->" + status;
}
}
}

151
Core/BreadcrumbsManager.cs Normal file
View file

@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using System.Web;
namespace ScrewTurn.Wiki {
/// <summary>
/// Manages navigation Breadcrumbs.
/// </summary>
public class BreadcrumbsManager {
private const int MaxPages = 10;
private const string CookieName = "ScrewTurnWikiBreadcrumbs3";
private const string CookieValue = "B";
private List<PageInfo> pages;
/// <summary>
/// Gets the cookie.
/// </summary>
/// <returns>The cookie, or <c>null</c>.</returns>
private HttpCookie GetCookie() {
if(HttpContext.Current.Request != null) {
HttpCookie cookie = HttpContext.Current.Request.Cookies[CookieName];
return cookie;
}
else return null;
}
/// <summary>
/// Initializes a new instance of the <b>BreadcrumbsManager</b> class.
/// </summary>
public BreadcrumbsManager() {
pages = new List<PageInfo>(MaxPages);
HttpCookie cookie = GetCookie();
if(cookie != null && !string.IsNullOrEmpty(cookie.Values[CookieValue])) {
try {
foreach(string p in cookie.Values[CookieValue].Split('|')) {
PageInfo page = Pages.FindPage(p);
if(page != null) pages.Add(page);
}
}
catch { }
}
}
/// <summary>
/// Updates the cookie.
/// </summary>
private void UpdateCookie() {
HttpCookie cookie = GetCookie();
if(cookie == null) {
cookie = new HttpCookie(CookieName);
}
cookie.Path = Settings.CookiePath;
StringBuilder sb = new StringBuilder(MaxPages * 20);
for(int i = 0; i < pages.Count; i++) {
sb.Append(pages[i].FullName);
if(i != pages.Count - 1) sb.Append("|");
}
cookie.Values[CookieValue] = sb.ToString();
if(HttpContext.Current.Response != null) {
HttpContext.Current.Response.Cookies.Set(cookie);
}
if(HttpContext.Current.Request != null) {
HttpContext.Current.Request.Cookies.Set(cookie);
}
}
/// <summary>
/// Adds a Page to the Breadcrumbs trail.
/// </summary>
/// <param name="page">The Page to add.</param>
public void AddPage(PageInfo page) {
lock(this) {
int index = FindPage(page);
if(index != -1) pages.RemoveAt(index);
pages.Add(page);
if(pages.Count > MaxPages) pages.RemoveRange(0, pages.Count - MaxPages);
UpdateCookie();
}
}
/// <summary>
/// Finds a page by name.
/// </summary>
/// <param name="page">The page.</param>
/// <returns>The index in the collection.</returns>
private int FindPage(PageInfo page) {
lock(this) {
if(pages == null || pages.Count == 0) return -1;
PageNameComparer comp = new PageNameComparer();
for(int i = 0; i < pages.Count; i++) {
if(comp.Compare(pages[i], page) == 0) return i;
}
return -1;
}
}
/// <summary>
/// Removes a Page from the Breadcrumbs trail.
/// </summary>
/// <param name="page">The Page to remove.</param>
public void RemovePage(PageInfo page) {
lock(this) {
int index = FindPage(page);
if(index >= 0) pages.RemoveAt(index);
UpdateCookie();
}
}
/// <summary>
/// Clears the Breadcrumbs trail.
/// </summary>
public void Clear() {
lock(this) {
pages.Clear();
UpdateCookie();
}
}
/// <summary>
/// Gets all the Pages in the trail that still exist.
/// </summary>
public PageInfo[] AllPages {
get {
lock(this) {
List<PageInfo> newPages = new List<PageInfo>(pages.Count);
foreach(PageInfo p in pages) {
if(Pages.FindPage(p.FullName) != null) newPages.Add(p);
}
return newPages.ToArray();
}
}
}
}
}

126
Core/Cache.cs Normal file
View file

@ -0,0 +1,126 @@

using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Manages data cache.
/// </summary>
public static class Cache {
/// <summary>
/// Gets the cache provider.
/// </summary>
public static ICacheProviderV30 Provider {
get { return Collectors.CacheProviderCollector.GetProvider(Settings.DefaultCacheProvider); }
}
/// <summary>
/// Gets or sets the number of users online.
/// </summary>
public static int OnlineUsers {
get { return Provider.OnlineUsers; }
set { Provider.OnlineUsers = value; }
}
/// <summary>
/// Clears the pages cache.
/// </summary>
public static void ClearPageCache() {
Provider.ClearPageContentCache();
}
/// <summary>
/// Clears the pseudo cache.
/// </summary>
public static void ClearPseudoCache() {
Provider.ClearPseudoCache();
}
/// <summary>
/// Gets a cached <see cref="T:PageContent" />.
/// </summary>
/// <param name="page">The page to get the content of.</param>
/// <returns>The page content, or <c>null</c>.</returns>
public static PageContent GetPageContent(PageInfo page) {
if(page == null) return null;
return Provider.GetPageContent(page);
}
/// <summary>
/// Gets a cached formatted page content.
/// </summary>
/// <param name="page">The page to get the formatted content of.</param>
/// <returns>The formatted page content, or <c>null</c>.</returns>
public static string GetFormattedPageContent(PageInfo page) {
if(page == null) return null;
return Provider.GetFormattedPageContent(page);
}
/// <summary>
/// Sets the page content in cache.
/// </summary>
/// <param name="page">The page to set the content of.</param>
/// <param name="content">The content.</param>
public static void SetPageContent(PageInfo page, PageContent content) {
Provider.SetPageContent(page, content);
if(Provider.PageCacheUsage > Settings.CacheSize) {
Provider.CutCache(Settings.CacheCutSize);
}
}
/// <summary>
/// Sets the formatted page content in cache.
/// </summary>
/// <param name="page">The page to set the content of.</param>
/// <param name="content">The content.</param>
public static void SetFormattedPageContent(PageInfo page, string content) {
Provider.SetFormattedPageContent(page, content);
}
/// <summary>
/// Removes a page from the cache.
/// </summary>
/// <param name="page">The page to remove.</param>
public static void RemovePage(PageInfo page) {
Provider.RemovePage(page);
}
/// <summary>
/// Gets a pseudo cache item value.
/// </summary>
/// <param name="name">The name of the item to get the value of.</param>
/// <returns>The value of the item, or <c>null</c>.</returns>
public static string GetPseudoCacheValue(string name) {
return Provider.GetPseudoCacheValue(name);
}
/// <summary>
/// Sets a pseudo cache item value.
/// </summary>
/// <param name="name">The name of the item to set the value of.</param>
/// <param name="value">The value of the item.</param>
public static void SetPseudoCacheValue(string name, string value) {
Provider.SetPseudoCacheValue(name, value);
}
/// <summary>
/// Gets the number of pages currently in the page cache.
/// </summary>
public static int PageCacheUsage {
get { return Provider.PageCacheUsage; }
}
/// <summary>
/// Gets the number of formatted pages currently in the page cache.
/// </summary>
public static int FormattedPageCacheUsage {
get { return Provider.FormatterPageCacheUsage; }
}
}
}

539
Core/CacheProvider.cs Normal file
View file

@ -0,0 +1,539 @@
using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Implements a local cache provider. All instance members are thread-safe.
/// </summary>
public class CacheProvider : ICacheProviderV30 {
private readonly ComponentInformation _info =
new ComponentInformation("Local Cache Provider", "ScrewTurn Software", Settings.WikiVersion, "http://www.screwturn.eu", null);
private IHostV30 _host;
// Implements the pseudo cache
private Dictionary<string, string> _pseudoCache;
// Contains the page contents
private Dictionary<PageInfo, PageContent> _pageContentCache;
// Contains the partially-formatted page content
private Dictionary<PageInfo, string> _formattedContentCache;
// Records, for each page, how many times a page has been requested,
// limited to page contents (not formatted content)
private Dictionary<PageInfo, int> _pageCacheUsage;
private int _onlineUsers = 0;
private List<EditingSession> _sessions;
// Key is lowercase, invariant culture
private Dictionary<string, string> _redirections;
/// <summary>
/// Initializes the Storage Provider.
/// </summary>
/// <param name="host">The Host of the Component.</param>
/// <param name="config">The Configuration data, if any.</param>
/// <exception cref="ArgumentNullException">If <b>host</b> or <b>config</b> are <c>null</c>.</exception>
/// <exception cref="InvalidConfigurationException">If <b>config</b> is not valid or is incorrect.</exception>
public void Init(IHostV30 host, string config) {
if(host == null) throw new ArgumentNullException("host");
if(config == null) throw new ArgumentNullException("config");
_host = host;
int s = int.Parse(host.GetSettingValue(SettingName.CacheSize));
// Initialize pseudo cache
_pseudoCache = new Dictionary<string, string>(10);
// Initialize page content cache
_pageContentCache = new Dictionary<PageInfo, PageContent>(s);
_pageCacheUsage = new Dictionary<PageInfo, int>(s);
// Initialize formatted page content cache
_formattedContentCache = new Dictionary<PageInfo, string>(s);
_sessions = new List<EditingSession>(50);
_redirections = new Dictionary<string, string>(50);
}
/// <summary>
/// Method invoked on shutdown.
/// </summary>
/// <remarks>This method might not be invoked in some cases.</remarks>
public void Shutdown() {
ClearPseudoCache();
ClearPageContentCache();
}
/// <summary>
/// Gets or sets the number of users online.
/// </summary>
public int OnlineUsers {
get {
lock(this) {
return _onlineUsers;
}
}
set {
lock(this) {
_onlineUsers = value;
}
}
}
/// <summary>
/// Gets the Information about the Provider.
/// </summary>
public ComponentInformation Information {
get { return _info; }
}
/// <summary>
/// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
/// </summary>
public string ConfigHelpHtml {
get { return null; }
}
/// <summary>
/// Gets the value of a Pseudo-cache item, previously stored in the cache.
/// </summary>
/// <param name="name">The name of the item being requested.</param>
/// <returns>The value of the item, or <c>null</c> if the item is not found.</returns>
/// <exception cref="ArgumentNullException">If <b>name</b> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>name</b> is empty.</exception>
public string GetPseudoCacheValue(string name) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
string value = null;
lock(_pseudoCache) {
_pseudoCache.TryGetValue(name, out value);
}
return value;
}
/// <summary>
/// Sets the value of a Pseudo-cache item.
/// </summary>
/// <param name="name">The name of the item being stored.</param>
/// <param name="value">The value of the item. If the value is <c>null</c>, then the item should be removed from the cache.</param>
/// <exception cref="ArgumentNullException">If <b>name</b> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>name</b> is empty.</exception>
public void SetPseudoCacheValue(string name, string value) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
lock(_pseudoCache) {
if(value == null) _pseudoCache.Remove(name);
else _pseudoCache[name] = value;
}
}
/// <summary>
/// Gets the Content of a Page, previously stored in cache.
/// </summary>
/// <param name="pageInfo">The Page Info object related to the Content being requested.</param>
/// <returns>The Page Content object, or <c>null</c> if the item is not found.</returns>
/// <exception cref="ArgumentNullException">If <b>pageInfo</b> is <c>null</c>.</exception>
public PageContent GetPageContent(PageInfo pageInfo) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
PageContent value = null;
lock(_pageContentCache) {
if(_pageContentCache.TryGetValue(pageInfo, out value)) {
_pageCacheUsage[pageInfo]++;
}
}
return value;
}
/// <summary>
/// Sets the Content of a Page.
/// </summary>
/// <param name="pageInfo">The Page Info object related to the Content being stored.</param>
/// <param name="content">The Content of the Page.</param>
/// <exception cref="ArgumentNullException">If <b>pageInfo</b> or <b>content</b> are <c>null</c>.</exception>
public void SetPageContent(PageInfo pageInfo, PageContent content) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
if(content == null) throw new ArgumentNullException("content");
lock(_pageContentCache) {
_pageContentCache[pageInfo] = content;
lock(_pageCacheUsage) {
_pageCacheUsage[pageInfo] = 0;
}
}
}
/// <summary>
/// Gets the partially-formatted content (text) of a Page, previously stored in the cache.
/// </summary>
/// <param name="pageInfo">The Page Info object related to the content being requested.</param>
/// <returns>The partially-formatted content, or <c>null</c> if the item is not found.</returns>
/// <exception cref="ArgumentNullException">If <b>pageInfo</b> is <c>null</c>.</exception>
public string GetFormattedPageContent(PageInfo pageInfo) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
string value = null;
lock(_formattedContentCache) {
_formattedContentCache.TryGetValue(pageInfo, out value);
}
return value;
}
/// <summary>
/// Sets the partially-preformatted content (text) of a Page.
/// </summary>
/// <param name="pageInfo">The Page Info object related to the content being stored.</param>
/// <param name="content">The partially-preformatted content.</param>
/// <exception cref="ArgumentNullException">If <b>pageInfo</b> or <b>content</b> are <c>null</c>.</exception>
public void SetFormattedPageContent(PageInfo pageInfo, string content) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
if(content == null) throw new ArgumentNullException("content");
lock(_formattedContentCache) {
_formattedContentCache[pageInfo] = content;
}
}
/// <summary>
/// Removes a Page from the cache.
/// </summary>
/// <param name="pageInfo">The Page Info object related to the Page that has to be removed.</param>
/// <exception cref="ArgumentNullException">If <b>pageInfo</b> is <c>null</c>.</exception>
public void RemovePage(PageInfo pageInfo) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
// In this order
lock(_formattedContentCache) {
_formattedContentCache.Remove(pageInfo);
}
lock(_pageContentCache) {
_pageContentCache.Remove(pageInfo);
}
lock(_pageCacheUsage) {
_pageCacheUsage.Remove(pageInfo);
}
}
/// <summary>
/// Clears the Page Content cache.
/// </summary>
public void ClearPageContentCache() {
// In this order
lock(_formattedContentCache) {
_formattedContentCache.Clear();
}
lock(_pageContentCache) {
_pageContentCache.Clear();
}
lock(_pageCacheUsage) {
_pageCacheUsage.Clear();
}
}
/// <summary>
/// Clears the Pseudo-Cache.
/// </summary>
public void ClearPseudoCache() {
lock(_pseudoCache) {
_pseudoCache.Clear();
}
}
/// <summary>
/// Reduces the size of the Page Content cache, removing the least-recently used items.
/// </summary>
/// <param name="cutSize">The number of Pages to remove.</param>
/// <exception cref="ArgumentOutOfRangeException">If <b>cutSize</b> is less than or equal to zero.</exception>
public void CutCache(int cutSize) {
if(cutSize <= 0) throw new ArgumentOutOfRangeException("cutSize", "Cut Size should be greater than zero");
lock(_pageContentCache) {
// TODO: improve performance - now the operation is O(cache_size^2)
for(int i = 0; i < cutSize; i++) {
PageInfo key = null;
int min = int.MaxValue;
// Find the page that has been requested the least times
foreach(PageInfo p in _pageCacheUsage.Keys) {
if(_pageCacheUsage[p] < min) {
key = p;
min = _pageCacheUsage[p];
}
}
// This is necessary to avoid infinite loops
if(key == null) {
break;
}
// Remove the page from cache
RemovePage(key);
}
}
}
/// <summary>
/// Gets the number of Pages whose content is currently stored in the cache.
/// </summary>
public int PageCacheUsage {
get {
lock(_pageContentCache) {
return _pageContentCache.Count;
}
}
}
/// <summary>
/// Gets the numer of Pages whose formatted content is currently stored in the cache.
/// </summary>
public int FormatterPageCacheUsage {
get {
lock(_formattedContentCache) {
return _formattedContentCache.Count;
}
}
}
/// <summary>
/// Adds or updates an editing session.
/// </summary>
/// <param name="page">The edited Page.</param>
/// <param name="user">The User who is editing the Page.</param>
/// <exception cref="ArgumentNullException">If <b>page</b> or <b>user</b> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>page</b> or <b>user</b> are empty.</exception>
public void RenewEditingSession(string page, string user) {
if(page == null) throw new ArgumentNullException("page");
if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page");
if(user == null) throw new ArgumentNullException("user");
if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user");
lock(this) {
bool found = false;
for(int i = 0; i < _sessions.Count; i++) {
if(_sessions[i].Page == page && _sessions[i].User.Equals(user)) {
_sessions[i].Renew();
found = true;
break;
}
}
if(!found) _sessions.Add(new EditingSession(page, user));
}
}
/// <summary>
/// Cancels an editing session.
/// </summary>
/// <param name="page">The Page.</param>
/// <param name="user">The User.</param>
/// <exception cref="ArgumentNullException">If <b>page</b> or <b>user</b> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>page</b> or <b>user</b> are empty.</exception>
public void CancelEditingSession(string page, string user) {
if(page == null) throw new ArgumentNullException("page");
if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page");
if(user == null) throw new ArgumentNullException("user");
if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user");
lock(this) {
for(int i = 0; i < _sessions.Count; i++) {
if(_sessions[i].Page == page && _sessions[i].User.Equals(user)) {
_sessions.Remove(_sessions[i]);
break;
}
}
}
}
/// <summary>
/// Finds whether a Page is being edited by a different user.
/// </summary>
/// <param name="page">The Page.</param>
/// <param name="currentUser">The User who is requesting the status of the Page.</param>
/// <returns>True if the Page is being edited by another User.</returns>
/// <exception cref="ArgumentNullException">If <b>page</b> or <b>currentUser</b> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>page</b> or <b>currentUser</b> are empty.</exception>
public bool IsPageBeingEdited(string page, string currentUser) {
if(page == null) throw new ArgumentNullException("page");
if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page");
if(currentUser == null) throw new ArgumentNullException("currentUser");
if(currentUser.Length == 0) throw new ArgumentException("Current User cannot be empty", "currentUser");
lock(this) {
int timeout = int.Parse(_host.GetSettingValue(SettingName.EditingSessionTimeout));
DateTime limit = DateTime.Now.AddSeconds(-(timeout + 5)); // Allow 5 seconds for network delays
for(int i = 0; i < _sessions.Count; i++) {
if(_sessions[i].Page == page && !_sessions[i].User.Equals(currentUser)) {
if(_sessions[i].LastContact.CompareTo(limit) >= 0) {
// Page is being edited
return true;
}
else {
// Lost contact
_sessions.Remove(_sessions[i]);
return false;
}
}
}
}
return false;
}
/// <summary>
/// Gets the username of the user who's editing a page.
/// </summary>
/// <param name="page">The page.</param>
/// <returns>The username.</returns>
/// <exception cref="ArgumentNullException">If <b>page</b> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>page</b> is empty.</exception>
public string WhosEditing(string page) {
if(page == null) throw new ArgumentNullException("page");
if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page");
lock(this) {
foreach(EditingSession s in _sessions) {
if(s.Page == page) return s.User;
}
}
return "";
}
/// <summary>
/// Adds the redirection information for a page (overwrites the previous value, if any).
/// </summary>
/// <param name="source">The source page.</param>
/// <param name="destination">The destination page.</param>
/// <exception cref="ArgumentNullException">If <b>source</b> or <b>destination</b> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>source</b> or <b>destination</b> are empty.</exception>
public void AddRedirection(string source, string destination) {
if(source == null) throw new ArgumentNullException("source");
if(source.Length == 0) throw new ArgumentException("Source cannot be empty", "source");
if(destination == null) throw new ArgumentNullException("destination");
if(destination.Length == 0) throw new ArgumentException("Destination cannot be empty", "destination");
lock(_redirections) {
_redirections[source.ToLowerInvariant()] = destination;
}
}
/// <summary>
/// Gets the destination of a redirection.
/// </summary>
/// <param name="source">The source page.</param>
/// <returns>The destination page, if any, <c>null</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <b>source</b> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>source</b> is empty.</exception>
public string GetRedirectionDestination(string source) {
if(source == null) throw new ArgumentNullException("source");
if(source.Length == 0) throw new ArgumentException("Source cannot be empty", "source");
string dest = null;
lock(_redirections) {
_redirections.TryGetValue(source.ToLowerInvariant(), out dest);
}
return dest;
}
/// <summary>
/// Removes a pge from both sources and destinations.
/// </summary>
/// <param name="name">The name of the page.</param>
/// <exception cref="ArgumentNullException">If <b>name</b> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>name</b> is empty.</exception>
public void RemovePageFromRedirections(string name) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
name = name.ToLowerInvariant();
lock(_redirections) {
_redirections.Remove(name);
List<string> keysToRemove = new List<string>(10);
foreach(KeyValuePair<string, string> pair in _redirections) {
if(pair.Value.ToLowerInvariant() == name) keysToRemove.Add(pair.Key);
}
foreach(string key in keysToRemove) {
_redirections.Remove(key);
}
}
}
/// <summary>
/// Clears all the redirections information.
/// </summary>
public void ClearRedirections() {
lock(_redirections) {
_redirections.Clear();
}
}
}
/// <summary>
/// Represents an Editing Session.
/// </summary>
public class EditingSession {
private string page;
private string user;
private DateTime lastContact;
/// <summary>
/// Initializes a new instance of the <b>EditingSession</b> class.
/// </summary>
/// <param name="page">The edited Page.</param>
/// <param name="user">The User who is editing the Page.</param>
public EditingSession(string page, string user) {
this.page = page;
this.user = user;
lastContact = DateTime.Now;
}
/// <summary>
/// Gets the edited Page.
/// </summary>
public string Page {
get { return page; }
}
/// <summary>
/// Gets the User.
/// </summary>
public string User {
get { return user; }
}
/// <summary>
/// Sets the Last Contact to now.
/// </summary>
public void Renew() {
lastContact = DateTime.Now;
}
/// <summary>
/// Gets the Last Contact Date/Time.
/// </summary>
public DateTime LastContact {
get { return lastContact; }
}
}
}

268
Core/Collectors.cs Normal file
View file

@ -0,0 +1,268 @@
using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Contains instances of the Providers Collectors.
/// </summary>
public static class Collectors {
// The following static instances are "almost" thread-safe because they are set at startup and never changed
// The ProviderCollector generic class is fully thread-safe
/// <summary>
/// Contains the file names of the DLLs containing each provider (provider->file).
/// </summary>
public static Dictionary<string, string> FileNames;
/// <summary>
/// The settings storage provider.
/// </summary>
public static ISettingsStorageProviderV30 SettingsProvider;
/// <summary>
/// The Users Provider Collector instance.
/// </summary>
public static ProviderCollector<IUsersStorageProviderV30> UsersProviderCollector;
/// <summary>
/// The Pages Provider Collector instance.
/// </summary>
public static ProviderCollector<IPagesStorageProviderV30> PagesProviderCollector;
/// <summary>
/// The Files Provider Collector instance.
/// </summary>
public static ProviderCollector<IFilesStorageProviderV30> FilesProviderCollector;
/// <summary>
/// The Formatter Provider Collector instance.
/// </summary>
public static ProviderCollector<IFormatterProviderV30> FormatterProviderCollector;
/// <summary>
/// The Cache Provider Collector instance.
/// </summary>
public static ProviderCollector<ICacheProviderV30> CacheProviderCollector;
/// <summary>
/// The Disabled Users Provider Collector instance.
/// </summary>
public static ProviderCollector<IUsersStorageProviderV30> DisabledUsersProviderCollector;
/// <summary>
/// The Disabled Files Provider Collector instance.
/// </summary>
public static ProviderCollector<IFilesStorageProviderV30> DisabledFilesProviderCollector;
/// <summary>
/// The Disabled Pages Provider Collector instance.
/// </summary>
public static ProviderCollector<IPagesStorageProviderV30> DisabledPagesProviderCollector;
/// <summary>
/// The Disabled Formatter Provider Collector instance.
/// </summary>
public static ProviderCollector<IFormatterProviderV30> DisabledFormatterProviderCollector;
/// <summary>
/// The Disabled Cache Provider Collector instance.
/// </summary>
public static ProviderCollector<ICacheProviderV30> DisabledCacheProviderCollector;
/// <summary>
/// Finds a provider.
/// </summary>
/// <param name="typeName">The provider type name.</param>
/// <param name="enabled">A value indicating whether the provider is enabled.</param>
/// <param name="canDisable">A value indicating whether the provider can be disabled.</param>
/// <returns>The provider, or <c>null</c>.</returns>
public static IProviderV30 FindProvider(string typeName, out bool enabled, out bool canDisable) {
enabled = true;
canDisable = true;
IProviderV30 prov = null;
prov = PagesProviderCollector.GetProvider(typeName);
canDisable = typeName != Settings.DefaultPagesProvider;
if(prov == null) {
prov = DisabledPagesProviderCollector.GetProvider(typeName);
if(prov != null) enabled = false;
}
if(prov != null) return prov;
prov = UsersProviderCollector.GetProvider(typeName);
canDisable = typeName != Settings.DefaultUsersProvider;
if(prov == null) {
prov = DisabledUsersProviderCollector.GetProvider(typeName);
if(prov != null) enabled = false;
}
if(prov != null) return prov;
prov = FilesProviderCollector.GetProvider(typeName);
canDisable = typeName != Settings.DefaultFilesProvider;
if(prov == null) {
prov = DisabledFilesProviderCollector.GetProvider(typeName);
if(prov != null) enabled = false;
}
if(prov != null) return prov;
prov = CacheProviderCollector.GetProvider(typeName);
canDisable = typeName != Settings.DefaultCacheProvider;
if(prov == null) {
prov = DisabledCacheProviderCollector.GetProvider(typeName);
if(prov != null) enabled = false;
}
if(prov != null) return prov;
prov = FormatterProviderCollector.GetProvider(typeName);
if(prov == null) {
prov = DisabledFormatterProviderCollector.GetProvider(typeName);
if(prov != null) enabled = false;
}
if(prov != null) return prov;
return null;
}
/// <summary>
/// Tries to unload a provider.
/// </summary>
/// <param name="typeName">The provider.</param>
public static void TryUnload(string typeName) {
bool enabled, canDisable;
IProviderV30 prov = FindProvider(typeName, out enabled, out canDisable);
PagesProviderCollector.RemoveProvider(prov as IPagesStorageProviderV30);
DisabledPagesProviderCollector.RemoveProvider(prov as IPagesStorageProviderV30);
UsersProviderCollector.RemoveProvider(prov as IUsersStorageProviderV30);
DisabledUsersProviderCollector.RemoveProvider(prov as IUsersStorageProviderV30);
FilesProviderCollector.RemoveProvider(prov as IFilesStorageProviderV30);
DisabledFilesProviderCollector.RemoveProvider(prov as IFilesStorageProviderV30);
CacheProviderCollector.RemoveProvider(prov as ICacheProviderV30);
DisabledCacheProviderCollector.RemoveProvider(prov as ICacheProviderV30);
FormatterProviderCollector.RemoveProvider(prov as IFormatterProviderV30);
DisabledFormatterProviderCollector.RemoveProvider(prov as IFormatterProviderV30);
}
/// <summary>
/// Tries to disable a provider.
/// </summary>
/// <param name="typeName">The provider.</param>
public static void TryDisable(string typeName) {
IProviderV30 prov = null;
prov = PagesProviderCollector.GetProvider(typeName);
if(prov != null) {
DisabledPagesProviderCollector.AddProvider((IPagesStorageProviderV30)prov);
PagesProviderCollector.RemoveProvider((IPagesStorageProviderV30)prov);
return;
}
prov = UsersProviderCollector.GetProvider(typeName);
if(prov != null) {
DisabledUsersProviderCollector.AddProvider((IUsersStorageProviderV30)prov);
UsersProviderCollector.RemoveProvider((IUsersStorageProviderV30)prov);
return;
}
prov = FilesProviderCollector.GetProvider(typeName);
if(prov != null) {
DisabledFilesProviderCollector.AddProvider((IFilesStorageProviderV30)prov);
FilesProviderCollector.RemoveProvider((IFilesStorageProviderV30)prov);
return;
}
prov = CacheProviderCollector.GetProvider(typeName);
if(prov != null) {
DisabledCacheProviderCollector.AddProvider((ICacheProviderV30)prov);
CacheProviderCollector.RemoveProvider((ICacheProviderV30)prov);
return;
}
prov = FormatterProviderCollector.GetProvider(typeName);
if(prov != null) {
DisabledFormatterProviderCollector.AddProvider((IFormatterProviderV30)prov);
FormatterProviderCollector.RemoveProvider((IFormatterProviderV30)prov);
return;
}
}
/// <summary>
/// Tries to enable a provider.
/// </summary>
/// <param name="typeName">The provider.</param>
public static void TryEnable(string typeName) {
IProviderV30 prov = null;
prov = DisabledPagesProviderCollector.GetProvider(typeName);
if(prov != null) {
PagesProviderCollector.AddProvider((IPagesStorageProviderV30)prov);
DisabledPagesProviderCollector.RemoveProvider((IPagesStorageProviderV30)prov);
return;
}
prov = DisabledUsersProviderCollector.GetProvider(typeName);
if(prov != null) {
UsersProviderCollector.AddProvider((IUsersStorageProviderV30)prov);
DisabledUsersProviderCollector.RemoveProvider((IUsersStorageProviderV30)prov);
return;
}
prov = DisabledFilesProviderCollector.GetProvider(typeName);
if(prov != null) {
FilesProviderCollector.AddProvider((IFilesStorageProviderV30)prov);
DisabledFilesProviderCollector.RemoveProvider((IFilesStorageProviderV30)prov);
return;
}
prov = DisabledCacheProviderCollector.GetProvider(typeName);
if(prov != null) {
CacheProviderCollector.AddProvider((ICacheProviderV30)prov);
DisabledCacheProviderCollector.RemoveProvider((ICacheProviderV30)prov);
return;
}
prov = DisabledFormatterProviderCollector.GetProvider(typeName);
if(prov != null) {
FormatterProviderCollector.AddProvider((IFormatterProviderV30)prov);
DisabledFormatterProviderCollector.RemoveProvider((IFormatterProviderV30)prov);
return;
}
}
/// <summary>
/// Gets the names of all known providers, both enabled and disabled.
/// </summary>
/// <returns>The names of the known providers.</returns>
public static string[] GetAllProviders() {
List<string> result = new List<string>(20);
foreach(IProviderV30 prov in PagesProviderCollector.AllProviders) result.Add(prov.GetType().FullName);
foreach(IProviderV30 prov in DisabledPagesProviderCollector.AllProviders) result.Add(prov.GetType().FullName);
foreach(IProviderV30 prov in UsersProviderCollector.AllProviders) result.Add(prov.GetType().FullName);
foreach(IProviderV30 prov in DisabledUsersProviderCollector.AllProviders) result.Add(prov.GetType().FullName);
foreach(IProviderV30 prov in FilesProviderCollector.AllProviders) result.Add(prov.GetType().FullName);
foreach(IProviderV30 prov in DisabledFilesProviderCollector.AllProviders) result.Add(prov.GetType().FullName);
foreach(IProviderV30 prov in CacheProviderCollector.AllProviders) result.Add(prov.GetType().FullName);
foreach(IProviderV30 prov in DisabledCacheProviderCollector.AllProviders) result.Add(prov.GetType().FullName);
foreach(IProviderV30 prov in FormatterProviderCollector.AllProviders) result.Add(prov.GetType().FullName);
foreach(IProviderV30 prov in DisabledFormatterProviderCollector.AllProviders) result.Add(prov.GetType().FullName);
return result.ToArray();
}
}
}

57
Core/Collisions.cs Normal file
View file

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Manages Page Editing collisions.
/// </summary>
public static class Collisions {
/// <summary>
/// The refresh interval used for renewing the sessions.
/// </summary>
public const int EditingSessionTimeout = 20;
/// <summary>
/// Adds or updates an editing session.
/// </summary>
/// <param name="page">The edited Page.</param>
/// <param name="user">The User who is editing the Page.</param>
public static void RenewEditingSession(PageInfo page, string user) {
Cache.Provider.RenewEditingSession(page.FullName, user);
}
/// <summary>
/// Cancels an editing session.
/// </summary>
/// <param name="page">The Page.</param>
/// <param name="user">The User.</param>
public static void CancelEditingSession(PageInfo page, string user) {
Cache.Provider.CancelEditingSession(page.FullName, user);
}
/// <summary>
/// Finds whether a Page is being edited by a different user.
/// </summary>
/// <param name="page">The Page.</param>
/// <param name="currentUser">The User who is requesting the status of the Page.</param>
/// <returns>True if the Page is being edited by another User.</returns>
public static bool IsPageBeingEdited(PageInfo page, string currentUser) {
return Cache.Provider.IsPageBeingEdited(page.FullName, currentUser);
}
/// <summary>
/// Gets the username of the user who's editing a page.
/// </summary>
/// <param name="page">The page.</param>
/// <returns>The username.</returns>
public static string WhosEditing(PageInfo page) {
return Cache.Provider.WhosEditing(page.FullName);
}
}
}

105
Core/Content.cs Normal file
View file

@ -0,0 +1,105 @@
using System;
using System.Configuration;
using System.Web;
using System.Collections.Generic;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Contains the Contents.
/// </summary>
public static class Content {
/// <summary>
/// Gets a pseudo cache item value.
/// </summary>
/// <param name="name">The name of the item to retrieve the value of.</param>
/// <returns>The value of the item, or <c>null</c>.</returns>
public static string GetPseudoCacheValue(string name) {
return Cache.GetPseudoCacheValue(name);
}
/// <summary>
/// Sets a pseudo cache item value, only if the content cache is enabled.
/// </summary>
/// <param name="name">The name of the item to store the value of.</param>
/// <param name="value">The value of the item.</param>
public static void SetPseudoCacheValue(string name, string value) {
if(!Settings.DisableCache) {
Cache.SetPseudoCacheValue(name, value);
}
}
/// <summary>
/// Clears the pseudo cache.
/// </summary>
public static void ClearPseudoCache() {
Cache.ClearPseudoCache();
}
/// <summary>
/// Reads the Content of a Page.
/// </summary>
/// <param name="pageInfo">The Page.</param>
/// <param name="cached">Specifies whether the page has to be cached or not.</param>
/// <returns>The Page Content.</returns>
public static PageContent GetPageContent(PageInfo pageInfo, bool cached) {
PageContent result = Cache.GetPageContent(pageInfo);
if(result == null) {
result = pageInfo.Provider.GetContent(pageInfo);
if(result!= null && !result.IsEmpty()) {
if(cached && !pageInfo.NonCached && !Settings.DisableCache) {
Cache.SetPageContent(pageInfo, result);
}
}
}
// result should NEVER be null
if(result == null) {
Log.LogEntry("PageContent could not be retrieved for page " + pageInfo.FullName + " - returning empty", EntryType.Error, Log.SystemUsername);
result = PageContent.GetEmpty(pageInfo);
}
return result;
}
/// <summary>
/// Gets the formatted Page Content, properly handling content caching and the Formatting Pipeline.
/// </summary>
/// <param name="page">The Page to get the formatted Content of.</param>
/// <param name="cached">Specifies whether the formatted content has to be cached or not.</param>
/// <returns>The formatted content.</returns>
public static string GetFormattedPageContent(PageInfo page, bool cached) {
string content = Cache.GetFormattedPageContent(page);
if(content == null) {
PageContent pg = GetPageContent(page, cached);
string[] linkedPages;
content = FormattingPipeline.FormatWithPhase1And2(pg.Content, false, FormattingContext.PageContent, page, out linkedPages);
pg.LinkedPages = linkedPages;
if(!pg.IsEmpty() && cached && !page.NonCached && !Settings.DisableCache) {
Cache.SetFormattedPageContent(page, content);
}
}
return FormattingPipeline.FormatWithPhase3(content, FormattingContext.PageContent, page);
}
/// <summary>
/// Invalidates the cached Content of a Page.
/// </summary>
/// <param name="pageInfo">The Page to invalidate the cached content of.</param>
public static void InvalidatePage(PageInfo pageInfo) {
Cache.RemovePage(pageInfo);
}
/// <summary>
/// Invalidates all the cache Contents.
/// </summary>
public static void InvalidateAllPages() {
Cache.ClearPageCache();
}
}
}

141
Core/Core.csproj Normal file
View file

@ -0,0 +1,141 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{C353A35C-86D0-4154-9500-4F88CAAB29C3}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ScrewTurn.Wiki</RootNamespace>
<AssemblyName>ScrewTurn.Wiki.Core</AssemblyName>
<SignAssembly>false</SignAssembly>
<AssemblyOriginatorKeyFile>
</AssemblyOriginatorKeyFile>
<FileUpgradeFlags>
</FileUpgradeFlags>
<OldToolsVersion>2.0</OldToolsVersion>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<FileAlignment>512</FileAlignment>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DocumentationFile>bin\Debug\ScrewTurn.Wiki.Core.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<FileAlignment>512</FileAlignment>
<DocumentationFile>bin\Release\ScrewTurn.Wiki.Core.xml</DocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Web" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\AssemblyVersion.cs">
<Link>AssemblyVersion.cs</Link>
</Compile>
<Compile Include="AclStorer.cs" />
<Compile Include="AuthChecker.cs" />
<Compile Include="AuthStatus.cs" />
<Compile Include="AuthTools.cs" />
<Compile Include="AuthWriter.cs" />
<Compile Include="BreadcrumbsManager.cs" />
<Compile Include="Cache.cs" />
<Compile Include="CacheProvider.cs" />
<Compile Include="Collectors.cs" />
<Compile Include="Collisions.cs" />
<Compile Include="Content.cs" />
<Compile Include="DataMigrator.cs" />
<Compile Include="Defaults.cs" />
<Compile Include="DiffTools.cs" />
<Compile Include="EmailTools.cs" />
<Compile Include="FileDocument.cs" />
<Compile Include="FilesAndAttachments.cs" />
<Compile Include="FilesStorageProvider.cs" />
<Compile Include="Formatter.cs" />
<Compile Include="FormattingPipeline.cs" />
<Compile Include="Hash.cs" />
<Compile Include="Host.cs" />
<Compile Include="IndexStorer.cs" />
<Compile Include="ITranslator.cs" />
<Compile Include="LocalPageInfo.cs" />
<Compile Include="LocalProvidersTools.cs" />
<Compile Include="LocalUserInfo.cs" />
<Compile Include="Log.cs" />
<Compile Include="MimeTypes.cs" />
<Compile Include="NavigationPaths.cs" />
<Compile Include="PageAttachmentDocument.cs" />
<Compile Include="Pages.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="PagesStorageProvider.cs" />
<Compile Include="AuthReader.cs" />
<Compile Include="Preferences.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProviderCollector.cs" />
<Compile Include="ProviderLoader.cs" />
<Compile Include="ProviderUpdater.cs" />
<Compile Include="RecentChanges.cs" />
<Compile Include="Redirections.cs" />
<Compile Include="ReverseFormatter.cs" />
<Compile Include="SearchTools.cs" />
<Compile Include="SessionFacade.cs" />
<Compile Include="Settings.cs" />
<Compile Include="SettingsStorageProvider.cs" />
<Compile Include="Snippets.cs" />
<Compile Include="StartupTools.cs" />
<Compile Include="SubjectInfo.cs" />
<Compile Include="Templates.cs" />
<Compile Include="Tools.cs" />
<Compile Include="Translator.cs" />
<Compile Include="TranslatorFlex.cs" />
<Compile Include="UrlTools.cs" />
<Compile Include="Users.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="UsersStorageProvider.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AclEngine\AclEngine.csproj">
<Project>{44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}</Project>
<Name>AclEngine</Name>
</ProjectReference>
<ProjectReference Include="..\PluginFramework\PluginFramework.csproj">
<Project>{531A83D6-76F9-4014-91C5-295818E2D948}</Project>
<Name>PluginFramework</Name>
</ProjectReference>
<ProjectReference Include="..\SearchEngine\SearchEngine.csproj">
<Project>{2DF980A6-4742-49B1-A090-DE79314644D0}</Project>
<Name>SearchEngine</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- 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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

384
Core/DataMigrator.cs Normal file
View file

@ -0,0 +1,384 @@
using System;
using System.Collections.Generic;
using System.IO;
using ScrewTurn.Wiki.AclEngine;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Provides methods for migrating data from a Provider to another.
/// </summary>
public static class DataMigrator {
/// <summary>
/// Migrates <b>all</b> the data from a Pages Provider to another one.
/// </summary>
/// <param name="source">The source Provider.</param>
/// <param name="destination">The destination Provider.</param>
public static void MigratePagesStorageProviderData(IPagesStorageProviderV30 source, IPagesStorageProviderV30 destination) {
// Move Snippets
Snippet[] snippets = source.GetSnippets();
for(int i = 0; i < snippets.Length; i++) {
destination.AddSnippet(snippets[i].Name, snippets[i].Content);
source.RemoveSnippet(snippets[i].Name);
}
// Move Content Templates
ContentTemplate[] templates = source.GetContentTemplates();
for(int i = 0; i < templates.Length; i++) {
destination.AddContentTemplate(templates[i].Name, templates[i].Content);
source.RemoveContentTemplate(templates[i].Name);
}
// Create namespaces
NamespaceInfo[] namespaces = source.GetNamespaces();
NamespaceInfo[] createdNamespaces = new NamespaceInfo[namespaces.Length];
for(int i = 0; i < namespaces.Length; i++) {
createdNamespaces[i] = destination.AddNamespace(namespaces[i].Name);
}
List<NamespaceInfo> sourceNamespaces = new List<NamespaceInfo>();
sourceNamespaces.Add(null);
sourceNamespaces.AddRange(namespaces);
int currentNamespaceIndex = 0;
foreach(NamespaceInfo currentNamespace in sourceNamespaces) {
// Load all nav paths now to avoid problems with missing pages from source provider
// after the pages have been moved already
NavigationPath[] sourceNavPaths = source.GetNavigationPaths(currentNamespace);
// Copy categories (removed from source later)
CategoryInfo[] sourceCats = source.GetCategories(currentNamespace);
for(int i = 0; i < sourceCats.Length; i++) {
destination.AddCategory(NameTools.GetNamespace(sourceCats[i].FullName), NameTools.GetLocalName(sourceCats[i].FullName));
}
// Move Pages
PageInfo[] pages = source.GetPages(currentNamespace);
for(int i = 0; i < pages.Length; i++) {
// Create Page
PageInfo newPage = destination.AddPage(NameTools.GetNamespace(pages[i].FullName),
NameTools.GetLocalName(pages[i].FullName), pages[i].CreationDateTime);
if(newPage == null) {
Log.LogEntry("Unable to move Page " + pages[i].FullName + " - Skipping", EntryType.Error, Log.SystemUsername);
continue;
}
// Get content and store it, without backup
PageContent c = source.GetContent(pages[i]);
destination.ModifyPage(newPage, c.Title, c.User, c.LastModified, c.Comment, c.Content, c.Keywords, c.Description, SaveMode.Normal);
// Move all the backups
int[] revs = source.GetBackups(pages[i]);
for(int k = 0; k < revs.Length; k++) {
c = source.GetBackupContent(pages[i], revs[k]);
destination.SetBackupContent(c, revs[k]);
}
// Move all messages
Message[] messages = source.GetMessages(pages[i]);
destination.BulkStoreMessages(newPage, messages);
// Bind current page (find all proper categories, and use them to bind the page)
List<string> pageCats = new List<string>();
for(int k = 0; k < sourceCats.Length; k++) {
for(int z = 0; z < sourceCats[k].Pages.Length; z++) {
if(sourceCats[k].Pages[z].Equals(newPage.FullName)) {
pageCats.Add(sourceCats[k].FullName);
break;
}
}
}
destination.RebindPage(newPage, pageCats.ToArray());
// Copy draft
PageContent draft = source.GetDraft(pages[i]);
if(draft != null) {
destination.ModifyPage(newPage, draft.Title, draft.User, draft.LastModified,
draft.Comment, draft.Content, draft.Keywords, draft.Description, SaveMode.Draft);
}
// Remove Page from source
source.RemovePage(pages[i]); // Also deletes the Messages
}
// Remove Categories from source
for(int i = 0; i < sourceCats.Length; i++) {
source.RemoveCategory(sourceCats[i]);
}
// Move navigation paths
List<PageInfo> newPages = new List<PageInfo>(destination.GetPages(currentNamespace == null ? null : createdNamespaces[currentNamespaceIndex]));
for(int i = 0; i < sourceNavPaths.Length; i++) {
PageInfo[] tmp = new PageInfo[sourceNavPaths[i].Pages.Length];
for(int k = 0; k < tmp.Length; k++) {
tmp[k] = newPages.Find(delegate(PageInfo p) { return p.FullName == sourceNavPaths[i].Pages[k]; });
}
destination.AddNavigationPath(NameTools.GetNamespace(sourceNavPaths[i].FullName),
NameTools.GetLocalName(sourceNavPaths[i].FullName), tmp);
source.RemoveNavigationPath(sourceNavPaths[i]);
}
if(currentNamespace != null) {
// Set default page
PageInfo defaultPage = currentNamespace.DefaultPage == null ? null :
newPages.Find(delegate(PageInfo p) { return p.FullName == currentNamespace.DefaultPage.FullName; });
destination.SetNamespaceDefaultPage(createdNamespaces[currentNamespaceIndex], defaultPage);
// Remove namespace from source
source.RemoveNamespace(currentNamespace);
currentNamespaceIndex++;
}
}
}
/// <summary>
/// Migrates all the User Accounts from a Provider to another.
/// </summary>
/// <param name="source">The source Provider.</param>
/// <param name="destination">The destination Provider.</param>
/// <param name="sendEmailNotification">A value indicating whether or not to send a notification email message to the moved users.</param>
public static void MigrateUsersStorageProviderData(IUsersStorageProviderV30 source, IUsersStorageProviderV30 destination, bool sendEmailNotification) {
// User groups
UserGroup[] groups = source.GetUserGroups();
foreach(UserGroup group in groups) {
destination.AddUserGroup(group.Name, group.Description);
}
// Users
UserInfo[] users = source.GetUsers();
MovedUser[] movedUsers = new MovedUser[users.Length];
for(int i = 0; i < users.Length; i++) {
// Generate new Password, create MovedUser object, add new User, delete old User
string password = Tools.GenerateRandomPassword();
movedUsers[i] = new MovedUser(users[i].Username, users[i].Email, users[i].DateTime);
UserInfo newUser = destination.AddUser(users[i].Username, users[i].DisplayName, password, users[i].Email, users[i].Active, users[i].DateTime);
// Membership
destination.SetUserMembership(newUser, users[i].Groups);
// User data
IDictionary<string, string> uData = source.RetrieveAllUserData(users[i]);
foreach(KeyValuePair<string, string> pair in uData) {
destination.StoreUserData(newUser, pair.Key, pair.Value);
}
source.RemoveUser(users[i]);
}
// Remove old groups
foreach(UserGroup group in groups) {
source.RemoveUserGroup(group);
}
if(sendEmailNotification) {
// Send Emails
for(int i = 0; i < movedUsers.Length; i++) {
Users.SendPasswordResetMessage(movedUsers[i].Username, movedUsers[i].Email, movedUsers[i].DateTime);
}
}
}
/// <summary>
/// Migrates all the stored files and page attachments from a Provider to another.
/// </summary>
/// <param name="source">The source Provider.</param>
/// <param name="destination">The destination Provider.</param>
/// <param name="settingsProvider">The settings storage provider that handles permissions.</param>
public static void MigrateFilesStorageProviderData(IFilesStorageProviderV30 source, IFilesStorageProviderV30 destination, ISettingsStorageProviderV30 settingsProvider) {
// Directories
MigrateDirectories(source, destination, "/", settingsProvider);
// Attachments
foreach(string page in source.GetPagesWithAttachments()) {
PageInfo pageInfo = new PageInfo(page, null, DateTime.Now);
string[] attachments = source.ListPageAttachments(pageInfo);
foreach(string attachment in attachments) {
// Copy file content
using(MemoryStream ms = new MemoryStream(1048576)) {
source.RetrievePageAttachment(pageInfo, attachment, ms, false);
ms.Seek(0, SeekOrigin.Begin);
destination.StorePageAttachment(pageInfo, attachment, ms, false);
}
// Copy download count
FileDetails fileDetails = source.GetPageAttachmentDetails(pageInfo, attachment);
destination.SetPageAttachmentRetrievalCount(pageInfo, attachment, fileDetails.RetrievalCount);
// Delete attachment
source.DeletePageAttachment(pageInfo, attachment);
}
}
}
private static void MigrateDirectories(IFilesStorageProviderV30 source, IFilesStorageProviderV30 destination, string current, ISettingsStorageProviderV30 settingsProvider) {
// Copy files
string[] files = source.ListFiles(current);
foreach(string file in files) {
// Copy file content
using(MemoryStream ms = new MemoryStream(1048576)) {
source.RetrieveFile(file, ms, false);
ms.Seek(0, SeekOrigin.Begin);
destination.StoreFile(file, ms, false);
}
// Copy download count
FileDetails fileDetails = source.GetFileDetails(file);
destination.SetFileRetrievalCount(file, fileDetails.RetrievalCount);
// Delete source file, if root
if(current == "/") {
source.DeleteFile(file);
}
}
settingsProvider.AclManager.RenameResource(
Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(source, current),
Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(destination, current));
// Copy directories
string[] directories = source.ListDirectories(current);
foreach(string dir in directories) {
destination.CreateDirectory(current, dir.Substring(dir.TrimEnd('/').LastIndexOf("/") + 1).Trim('/'));
MigrateDirectories(source, destination, dir, settingsProvider);
// Delete directory, if root
if(current == "/") {
source.DeleteDirectory(dir);
}
}
}
/// <summary>
/// Copies the settings from a Provider to another.
/// </summary>
/// <param name="source">The source Provider.</param>
/// <param name="destination">The destination Provider.</param>
/// <param name="knownNamespaces">The currently known page namespaces.</param>
/// <param name="knownPlugins">The currently known plugins.</param>
public static void CopySettingsStorageProviderData(ISettingsStorageProviderV30 source, ISettingsStorageProviderV30 destination,
string[] knownNamespaces, string[] knownPlugins) {
// Settings
destination.BeginBulkUpdate();
foreach(KeyValuePair<string, string> pair in source.GetAllSettings()) {
destination.SetSetting(pair.Key, pair.Value);
}
destination.EndBulkUpdate();
// Meta-data (global)
destination.SetMetaDataItem(MetaDataItem.AccountActivationMessage, null,
source.GetMetaDataItem(MetaDataItem.AccountActivationMessage, null));
destination.SetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null,
source.GetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null));
destination.SetMetaDataItem(MetaDataItem.LoginNotice, null,
source.GetMetaDataItem(MetaDataItem.LoginNotice, null));
destination.SetMetaDataItem(MetaDataItem.PageChangeMessage, null,
source.GetMetaDataItem(MetaDataItem.PageChangeMessage, null));
destination.SetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null,
source.GetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null));
// Meta-data (ns-specific)
List<string> namespacesToProcess = new List<string>();
namespacesToProcess.Add(null);
namespacesToProcess.AddRange(knownNamespaces);
foreach(string nspace in namespacesToProcess) {
destination.SetMetaDataItem(MetaDataItem.EditNotice, nspace,
source.GetMetaDataItem(MetaDataItem.EditNotice, nspace));
destination.SetMetaDataItem(MetaDataItem.Footer, nspace,
source.GetMetaDataItem(MetaDataItem.Footer, nspace));
destination.SetMetaDataItem(MetaDataItem.Header, nspace,
source.GetMetaDataItem(MetaDataItem.Header, nspace));
destination.SetMetaDataItem(MetaDataItem.HtmlHead, nspace,
source.GetMetaDataItem(MetaDataItem.HtmlHead, nspace));
destination.SetMetaDataItem(MetaDataItem.PageFooter, nspace,
source.GetMetaDataItem(MetaDataItem.PageFooter, nspace));
destination.SetMetaDataItem(MetaDataItem.PageHeader, nspace,
source.GetMetaDataItem(MetaDataItem.PageHeader, nspace));
destination.SetMetaDataItem(MetaDataItem.Sidebar, nspace,
source.GetMetaDataItem(MetaDataItem.Sidebar, nspace));
}
// Plugin assemblies
string[] assemblies = source.ListPluginAssemblies();
foreach(string asm in assemblies) {
destination.StorePluginAssembly(asm,
source.RetrievePluginAssembly(asm));
}
// Plugin status and config
foreach(string plug in knownPlugins) {
destination.SetPluginStatus(plug,
source.GetPluginStatus(plug));
destination.SetPluginConfiguration(plug,
source.GetPluginConfiguration(plug));
}
// Outgoing links
IDictionary<string, string[]> allLinks = source.GetAllOutgoingLinks();
foreach(KeyValuePair<string, string[]> link in allLinks) {
destination.StoreOutgoingLinks(link.Key, link.Value);
}
// ACLs
AclEntry[] allEntries = source.AclManager.RetrieveAllEntries();
foreach(AclEntry entry in allEntries) {
destination.AclManager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value);
}
}
}
/// <summary>
/// Contains username, email and registration date/time of a moved User account.
/// </summary>
public class MovedUser {
private string username, email;
private DateTime dateTime;
/// <summary>
/// Initializes a new instance of the <b>MovedUser</b> class.
/// </summary>
/// <param name="username">The esername.</param>
/// <param name="email">The email address.</param>
/// <param name="dateTime">The registration date/time.</param>
public MovedUser(string username, string email, DateTime dateTime) {
this.username = username;
this.email = email;
this.dateTime = dateTime;
}
/// <summary>
/// Gets the Username.
/// </summary>
public string Username {
get { return username; }
}
/// <summary>
/// Gets the Email.
/// </summary>
public string Email {
get { return email; }
}
/// <summary>
/// Gets the registration date/time.
/// </summary>
public DateTime DateTime {
get { return dateTime; }
}
}
}

178
Core/Defaults.cs Normal file
View file

@ -0,0 +1,178 @@

using System;
using System.Collections.Generic;
using System.Text;
namespace ScrewTurn.Wiki {
/// <summary>
/// Contains default values.
/// </summary>
public static class Defaults {
/// <summary>
/// The default content of the main page.
/// </summary>
public const string MainPageContent = @"Welcome to '''{WIKITITLE}'''!{BR}
This is the main page of your new ScrewTurn Wiki, created for you by the system.
You should [Edit.aspx?Page=MainPage|edit this page] and customize the contents. You can also [Edit.aspx|create a new page] from scratch.
If you need help, try to visit [http://www.screwturn.eu|our website] or [http://www.screwturn.eu/forum|our forum].
'''Warning''': remember to setup the ''admin'' account by editing the {{Web.config}} file placed in the root directory of the Wiki. It is ''extremely dangerous'' to keep the default password.";
/// <summary>
/// The default content of the main page of a sub-namespace.
/// </summary>
public const string MainPageContentForSubNamespace = @"Welcome to the '''{NAMESPACE}''' namespace of '''{WIKITITLE}'''!{BR}
This is the main page of the namespace, created for you by the system.
You should [Edit.aspx?Page={NAMESPACE}.MainPage|edit this page] and customize the contents. You can also [Edit.aspx|create a new page] from scratch.
If you need help, try to visit [http://www.screwturn.eu|our website] or [http://www.screwturn.eu/forum|our forum].";
/// <summary>
/// The default content of the account activation message.
/// </summary>
public const string AccountActivationMessageContent = @"Hi ##USERNAME## and welcome to ##WIKITITLE##!
You must activate your new ##WIKITITLE## Account within 24 hours, following the link below.
##ACTIVATIONLINK##
If you have any trouble, please contact us at our Email address, ##EMAILADDRESS## .
Thank you.
Best regards,
The ##WIKITITLE## Team.";
/// <summary>
/// The default content of the edit notice.
/// </summary>
public const string EditNoticeContent = @"Please '''do not''' include contents covered by copyright without the explicit permission of the Author. Always preview the result before saving.{BR}
If you are having trouble, please visit the [http://www.screwturn.eu/Help.aspx|Help section] at [http://www.screwturn.eu|ScrewTurn Software].";
/// <summary>
/// The default content of the footer.
/// </summary>
public const string FooterContent = @"<p class=""small"">[http://www.screwturn.eu|ScrewTurn Wiki] version {WIKIVERSION}. Some of the icons created by [http://www.famfamfam.com|FamFamFam].</p>";
/// <summary>
/// The default content of the header.
/// </summary>
public const string HeaderContent = @"<div style=""float: right;"">Welcome {USERNAME}, you are in: {NAMESPACEDROPDOWN} &bull; {LOGINLOGOUT}</div><h1>{WIKITITLE}</h1>";
/// <summary>
/// The default content of the password reset message.
/// </summary>
public const string PasswordResetProcedureMessageContent = @"Hi ##USERNAME##!
Your can change your password following the instructions you will see at this link:
##LINK##
If you have any trouble, please contact us at our Email address, ##EMAILADDRESS## .
Thank you.
Best regards,
The ##WIKITITLE## Team.";
/// <summary>
/// The default content of the sidebar.
/// </summary>
public const string SidebarContent = @"<div style=""float: right;"">
<a href=""RSS.aspx"" title=""Update notifications for {WIKITITLE} (RSS 2.0)""><img src=""{THEMEPATH}Images/RSS.png"" alt=""RSS"" /></a>
<a href=""RSS.aspx?Discuss=1"" title=""Update notifications for {WIKITITLE} Discussions (RSS 2.0)""><img src=""{THEMEPATH}Images/RSS-Discussion.png"" alt=""RSS"" /></a></div>
====Navigation====
* '''[MainPage|Main Page]'''
* [RandPage.aspx|Random Page]
* [Edit.aspx|Create a new Page]
* [AllPages.aspx|All Pages]
* [Category.aspx|Categories]
* [NavPath.aspx|Navigation Paths]
* [AdminHome.aspx|Administration]
* [Upload.aspx|File Management]
* [Register.aspx|Create Account]
<small>'''Search the wiki'''</small>{BR}
{SEARCHBOX}
[image|PoweredBy|Images/PoweredBy.png|http://www.screwturn.eu]";
/// <summary>
/// The default content of the sidebar of a sub-namespace.
/// </summary>
public const string SidebarContentForSubNamespace = @"<div style=""float: right;"">
<a href=""{NAMESPACE}.RSS.aspx"" title=""Update notifications for {WIKITITLE} ({NAMESPACE}) (RSS 2.0)""><img src=""{THEMEPATH}Images/RSS.png"" alt=""RSS"" /></a>
<a href=""{NAMESPACE}.RSS.aspx?Discuss=1"" title=""Update notifications for {WIKITITLE} Discussions ({NAMESPACE}) (RSS 2.0)""><img src=""{THEMEPATH}Images/RSS-Discussion.png"" alt=""RSS"" /></a></div>
====Navigation ({NAMESPACE})====
* '''[MainPage|Main Page]'''
* [++MainPage|Main Page (root)]
* [RandPage.aspx|Random Page]
* [Edit.aspx|Create a new Page]
* [AllPages.aspx|All Pages]
* [Category.aspx|Categories]
* [NavPath.aspx|Navigation Paths]
* [AdminHome.aspx|Administration]
* [Upload.aspx|File Management]
* [Register.aspx|Create Account]
<small>'''Search the wiki'''</small>{BR}
{SEARCHBOX}
[image|PoweredBy|Images/PoweredBy.png|http://www.screwturn.eu]";
/// <summary>
/// The default content of the page change email message.
/// </summary>
public const string PageChangeMessage = @"The page ""##PAGE##"" was modified by ##USER## on ##DATETIME##.
Author's comment: ##COMMENT##.
The page can be found at the following address:
##LINK##
Thank you.
Best regards,
The ##WIKITITLE## Team.";
/// <summary>
/// The default content of the discussion change email message.
/// </summary>
public const string DiscussionChangeMessage = @"A new message was posted on the page ""##PAGE##"" by ##USER## on ##DATETIME##.
The subject of the message is ""##SUBJECT##"" and it can be found at the following address:
##LINK##
Thank you.
Best regards,
The ##WIKITITLE## Team.";
/// <summary>
/// The default content of the approve draft email message.
/// </summary>
public const string ApproveDraftMessage = @"A draft for the page ""##PAGE##"" was created or modified by ##USER## on ##DATETIME## and is currently held for **approval**.
Author's comment: ##COMMENT##.
The draft can be found and edited at the following address:
##LINK##
You can directly approve or reject the draft at the following address:
##LINK2##
Please note that the draft will not be displayed until it is approved.
Thank you.
Best regards,
The ##WIKITITLE## Team.";
}
}

477
Core/DiffTools.cs Normal file
View file

@ -0,0 +1,477 @@
using System;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
namespace ScrewTurn.Wiki {
/// <summary>
/// Provides methods for diffing text and items.
/// </summary>
public static class DiffTools {
/// <summary>
/// Computes the difference between two revisions.
/// </summary>
/// <param name="rev1">The first revision.</param>
/// <param name="rev2">The second revision.</param>
/// <returns>The XHTML-formatted result.</returns>
public static string DiffRevisions(string rev1, string rev2) {
string[] aLines = rev1.Split('\n');
string[] bLines = rev2.Split('\n');
Difference.Item[] f = Difference.DiffText(rev1, rev2, true, false, false);
StringBuilder result = new StringBuilder();
result.Append(@"<table cellpadding=""0"" cellspacing=""0"" style=""color: #000000; background-color: #FFFFFF;"">");
int n = 0;
for(int fdx = 0; fdx < f.Length; fdx++) {
Difference.Item aItem = f[fdx];
// Write unchanged lines
while((n < aItem.StartB) && (n < bLines.Length)) {
result.Append(WriteLine(n, "", bLines[n]));
n++;
}
// Write deleted lines
for(int m = 0; m < aItem.deletedA; m++) {
result.Append(WriteLine(-1, "d", aLines[aItem.StartA + m]));
}
// Write inserted lines
while(n < aItem.StartB + aItem.insertedB) {
result.Append(WriteLine(n, "i", bLines[n]));
n++;
}
}
// Write the rest of unchanged lines
while(n < bLines.Length) {
result.Append(WriteLine(n, "", bLines[n]));
n++;
}
result.Append("</table>");
return result.ToString();
}
private static string WriteLine(int n, string typ, string line) {
StringBuilder sb = new StringBuilder();
sb.Append("<tr>");
sb.Append(@"<td valign=""top"" width=""30"" style=""font-family: Courier New, monospace;"">");
if(n >= 0) sb.Append(((int)(n + 1)).ToString());
else sb.Append("&nbsp;");
sb.Append("</td>");
sb.Append(@"<td valign=""top"" style=""font-family: Courier New, monospace;"">");
sb.Append(@"<div style=""");
switch(typ) {
case "i":
sb.Append("background-color: #88CC33;");
break;
case "d":
sb.Append("background-color: #FFDF66;");
break;
}
sb.Append(@""">" + HttpContext.Current.Server.HtmlEncode(line) + "</div>");
sb.Append("</td>");
sb.Append("</tr>");
return sb.ToString();
}
}
/// <summary>
/// O(ND) Difference Algorithm for C#
/// Created by Matthias Hertel, see http://www.mathertel.de
/// This work is licensed under a Creative Commons Attribution 2.0 Germany License.
/// see http://creativecommons.org/licenses/by/2.0/de/
/// </summary>
public class Difference {
/// <summary>details of one difference.</summary>
public struct Item {
/// <summary>Start Line number in Data A.</summary>
public int StartA;
/// <summary>Start Line number in Data B.</summary>
public int StartB;
/// <summary>Number of changes in Data A.</summary>
public int deletedA;
/// <summary>Number of changes in Data A.</summary>
public int insertedB;
} // Item
/// <summary>
/// Shortest Middle Snake Return Data
/// </summary>
private struct SMSRD {
internal int x, y;
// internal int u, v; // 2002.09.20: no need for 2 points
}
/// <summary>
/// Find the difference in 2 texts, comparing by textlines.
/// </summary>
/// <param name="TextA">A-version of the text (usualy the old one)</param>
/// <param name="TextB">B-version of the text (usualy the new one)</param>
/// <returns>Returns a array of Items that describe the differences.</returns>
public Item[] DiffText(string TextA, string TextB) {
return (DiffText(TextA, TextB, false, false, false));
} // DiffText
/// <summary>
/// Find the difference in 2 text documents, comparing by textlines.
/// The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents
/// each line is converted into a (hash) number. This hash-value is computed by storing all
/// textlines into a common hashtable so i can find dublicates in there, and generating a
/// new number each time a new textline is inserted.
/// </summary>
/// <param name="TextA">A-version of the text (usualy the old one)</param>
/// <param name="TextB">B-version of the text (usualy the new one)</param>
/// <param name="trimSpace">When set to true, all leading and trailing whitespace characters are stripped out before the comparation is done.</param>
/// <param name="ignoreSpace">When set to true, all whitespace characters are converted to a single space character before the comparation is done.</param>
/// <param name="ignoreCase">When set to true, all characters are converted to their lowercase equivivalence before the comparation is done.</param>
/// <returns>Returns a array of Items that describe the differences.</returns>
public static Item[] DiffText(string TextA, string TextB, bool trimSpace, bool ignoreSpace, bool ignoreCase) {
// prepare the input-text and convert to comparable numbers.
Hashtable h = new Hashtable(TextA.Length + TextB.Length);
// The A-Version of the data (original data) to be compared.
DiffData DataA = new DiffData(DiffCodes(TextA, h, trimSpace, ignoreSpace, ignoreCase));
// The B-Version of the data (modified data) to be compared.
DiffData DataB = new DiffData(DiffCodes(TextB, h, trimSpace, ignoreSpace, ignoreCase));
h = null; // free up hashtable memory (maybe)
LCS(DataA, 0, DataA.Length, DataB, 0, DataB.Length);
return CreateDiffs(DataA, DataB);
} // DiffText
/// <summary>
/// Find the difference in 2 arrays of integers.
/// </summary>
/// <param name="ArrayA">A-version of the numbers (usualy the old one)</param>
/// <param name="ArrayB">B-version of the numbers (usualy the new one)</param>
/// <returns>Returns a array of Items that describe the differences.</returns>
public static Item[] DiffInt(int[] ArrayA, int[] ArrayB) {
// The A-Version of the data (original data) to be compared.
DiffData DataA = new DiffData(ArrayA);
// The B-Version of the data (modified data) to be compared.
DiffData DataB = new DiffData(ArrayB);
LCS(DataA, 0, DataA.Length, DataB, 0, DataB.Length);
return CreateDiffs(DataA, DataB);
} // Diff
/// <summary>
/// Converts all textlines of the text into unique numbers for every unique textline
/// so further work can work only with simple numbers.
/// </summary>
/// <param name="aText">The input text</param>
/// <param name="h">This extern initialized hashtable is used for storing all ever used textlines.</param>
/// <param name="trimSpace">Ignore leading and trailing space characters</param>
/// <param name="ignoreSpace">Ignore spaces.</param>
/// <param name="ignoreCase">Ignore case.</param>
/// <returns>An array of integers.</returns>
private static int[] DiffCodes(string aText, Hashtable h, bool trimSpace, bool ignoreSpace, bool ignoreCase) {
// get all codes of the text
string[] Lines;
int[] Codes;
int lastUsedCode = h.Count;
object aCode;
string s;
// strip off all cr, only use lf as textline separator.
aText = aText.Replace("\r", "");
Lines = aText.Split('\n');
Codes = new int[Lines.Length];
for(int i = 0; i < Lines.Length; ++i) {
s = Lines[i];
if(trimSpace)
s = s.Trim();
if(ignoreSpace) {
s = Regex.Replace(s, "\\s+", " "); // TODO: optimization: faster blank removal.
}
if(ignoreCase)
s = s.ToLowerInvariant();
aCode = h[s];
if(aCode == null) {
lastUsedCode++;
h[s] = lastUsedCode;
Codes[i] = lastUsedCode;
}
else {
Codes[i] = (int)aCode;
} // if
} // for
return (Codes);
} // DiffCodes
/// <summary>
/// This is the algorithm to find the Shortest Middle Snake (SMS).
/// </summary>
/// <param name="DataA">sequence A</param>
/// <param name="LowerA">lower bound of the actual range in DataA</param>
/// <param name="UpperA">upper bound of the actual range in DataA (exclusive)</param>
/// <param name="DataB">sequence B</param>
/// <param name="LowerB">lower bound of the actual range in DataB</param>
/// <param name="UpperB">upper bound of the actual range in DataB (exclusive)</param>
/// <returns>a MiddleSnakeData record containing x,y and u,v</returns>
private static SMSRD SMS(DiffData DataA, int LowerA, int UpperA, DiffData DataB, int LowerB, int UpperB) {
SMSRD ret;
int MAX = DataA.Length + DataB.Length + 1;
int DownK = LowerA - LowerB; // the k-line to start the forward search
int UpK = UpperA - UpperB; // the k-line to start the reverse search
int Delta = (UpperA - LowerA) - (UpperB - LowerB);
bool oddDelta = (Delta & 1) != 0;
// vector for the (0,0) to (x,y) search
int[] DownVector = new int[2 * MAX + 2];
// vector for the (u,v) to (N,M) search
int[] UpVector = new int[2 * MAX + 2];
// The vectors in the publication accepts negative indexes. the vectors implemented here are 0-based
// and are access using a specific offset: UpOffset UpVector and DownOffset for DownVektor
int DownOffset = MAX - DownK;
int UpOffset = MAX - UpK;
int MaxD = ((UpperA - LowerA + UpperB - LowerB) / 2) + 1;
// Debug.Write(2, "SMS", String.Format("Search the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB));
// init vectors
DownVector[DownOffset + DownK + 1] = LowerA;
UpVector[UpOffset + UpK - 1] = UpperA;
for(int D = 0; D <= MaxD; D++) {
// Extend the forward path.
for(int k = DownK - D; k <= DownK + D; k += 2) {
// Debug.Write(0, "SMS", "extend forward path " + k.ToString());
// find the only or better starting point
int x, y;
if(k == DownK - D) {
x = DownVector[DownOffset + k + 1]; // down
}
else {
x = DownVector[DownOffset + k - 1] + 1; // a step to the right
if((k < DownK + D) && (DownVector[DownOffset + k + 1] >= x))
x = DownVector[DownOffset + k + 1]; // down
}
y = x - k;
// find the end of the furthest reaching forward D-path in diagonal k.
while((x < UpperA) && (y < UpperB) && (DataA.data[x] == DataB.data[y])) {
x++; y++;
}
DownVector[DownOffset + k] = x;
// overlap ?
if(oddDelta && (UpK - D < k) && (k < UpK + D)) {
if(UpVector[UpOffset + k] <= DownVector[DownOffset + k]) {
ret.x = DownVector[DownOffset + k];
ret.y = DownVector[DownOffset + k] - k;
// ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points
// ret.v = UpVector[UpOffset + k] - k;
return (ret);
} // if
} // if
} // for k
// Extend the reverse path.
for(int k = UpK - D; k <= UpK + D; k += 2) {
// Debug.Write(0, "SMS", "extend reverse path " + k.ToString());
// find the only or better starting point
int x, y;
if(k == UpK + D) {
x = UpVector[UpOffset + k - 1]; // up
}
else {
x = UpVector[UpOffset + k + 1] - 1; // left
if((k > UpK - D) && (UpVector[UpOffset + k - 1] < x))
x = UpVector[UpOffset + k - 1]; // up
} // if
y = x - k;
while((x > LowerA) && (y > LowerB) && (DataA.data[x - 1] == DataB.data[y - 1])) {
x--; y--; // diagonal
}
UpVector[UpOffset + k] = x;
// overlap ?
if(!oddDelta && (DownK - D <= k) && (k <= DownK + D)) {
if(UpVector[UpOffset + k] <= DownVector[DownOffset + k]) {
ret.x = DownVector[DownOffset + k];
ret.y = DownVector[DownOffset + k] - k;
// ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points
// ret.v = UpVector[UpOffset + k] - k;
return (ret);
} // if
} // if
} // for k
} // for D
throw new ApplicationException("the algorithm should never come here.");
} // SMS
/// <summary>
/// This is the divide-and-conquer implementation of the longes common-subsequence (LCS)
/// algorithm.
/// The published algorithm passes recursively parts of the A and B sequences.
/// To avoid copying these arrays the lower and upper bounds are passed while the sequences stay constant.
/// </summary>
/// <param name="DataA">sequence A</param>
/// <param name="LowerA">lower bound of the actual range in DataA</param>
/// <param name="UpperA">upper bound of the actual range in DataA (exclusive)</param>
/// <param name="DataB">sequence B</param>
/// <param name="LowerB">lower bound of the actual range in DataB</param>
/// <param name="UpperB">upper bound of the actual range in DataB (exclusive)</param>
private static void LCS(DiffData DataA, int LowerA, int UpperA, DiffData DataB, int LowerB, int UpperB) {
// Debug.Write(2, "LCS", String.Format("Analyse the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB));
// Fast walkthrough equal lines at the start
while(LowerA < UpperA && LowerB < UpperB && DataA.data[LowerA] == DataB.data[LowerB]) {
LowerA++; LowerB++;
}
// Fast walkthrough equal lines at the end
while(LowerA < UpperA && LowerB < UpperB && DataA.data[UpperA - 1] == DataB.data[UpperB - 1]) {
--UpperA; --UpperB;
}
if(LowerA == UpperA) {
// mark as inserted lines.
while(LowerB < UpperB)
DataB.modified[LowerB++] = true;
}
else if(LowerB == UpperB) {
// mark as deleted lines.
while(LowerA < UpperA)
DataA.modified[LowerA++] = true;
}
else {
// Find the middle snakea and length of an optimal path for A and B
SMSRD smsrd = SMS(DataA, LowerA, UpperA, DataB, LowerB, UpperB);
// Debug.Write(2, "MiddleSnakeData", String.Format("{0},{1}", smsrd.x, smsrd.y));
// The path is from LowerX to (x,y) and (x,y) ot UpperX
LCS(DataA, LowerA, smsrd.x, DataB, LowerB, smsrd.y);
LCS(DataA, smsrd.x, UpperA, DataB, smsrd.y, UpperB); // 2002.09.20: no need for 2 points
}
} // LCS()
/// <summary>Scan the tables of which lines are inserted and deleted,
/// producing an edit script in forward order.
/// </summary>
/// dynamic array
private static Item[] CreateDiffs(DiffData DataA, DiffData DataB) {
ArrayList a = new ArrayList();
Item aItem;
Item[] result;
int StartA, StartB;
int LineA, LineB;
LineA = 0;
LineB = 0;
while(LineA < DataA.Length || LineB < DataB.Length) {
if((LineA < DataA.Length) && (!DataA.modified[LineA])
&& (LineB < DataB.Length) && (!DataB.modified[LineB])) {
// equal lines
LineA++;
LineB++;
}
else {
// maybe deleted and/or inserted lines
StartA = LineA;
StartB = LineB;
while(LineA < DataA.Length && (LineB >= DataB.Length || DataA.modified[LineA]))
// while (LineA < DataA.Length && DataA.modified[LineA])
LineA++;
while(LineB < DataB.Length && (LineA >= DataA.Length || DataB.modified[LineB]))
// while (LineB < DataB.Length && DataB.modified[LineB])
LineB++;
if((StartA < LineA) || (StartB < LineB)) {
// store a new difference-item
aItem = new Item();
aItem.StartA = StartA;
aItem.StartB = StartB;
aItem.deletedA = LineA - StartA;
aItem.insertedB = LineB - StartB;
a.Add(aItem);
} // if
} // if
} // while
result = new Item[a.Count];
a.CopyTo(result);
return (result);
}
} // class Diff
/// <summary>Data on one input file being compared.
/// </summary>
internal class DiffData {
/// <summary>Number of elements (lines).</summary>
internal int Length;
/// <summary>Buffer of numbers that will be compared.</summary>
internal int[] data;
/// <summary>
/// Array of booleans that flag for modified data.
/// This is the result of the diff.
/// This means deletedA in the first Data or inserted in the second Data.
/// </summary>
internal bool[] modified;
/// <summary>
/// Initialize the Diff-Data buffer.
/// </summary>
/// <param name="initData">Reference to the buffer</param>
internal DiffData(int[] initData) {
data = initData;
Length = initData.Length;
modified = new bool[Length + 2];
} // DiffData
} // class DiffData
}

130
Core/EmailTools.cs Normal file
View file

@ -0,0 +1,130 @@
using System;
using System.Configuration;
using System.Net;
using System.Net.Mail;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Implements email-related tools.
/// </summary>
public static class EmailTools {
/// <summary>
/// Sends an email.
/// </summary>
/// <param name="recipient">The recipient.</param>
/// <param name="sender">The sender.</param>
/// <param name="subject">The subject.</param>
/// <param name="body">The message body.</param>
/// <param name="html"><c>true</c> if the body is HTML.</param>
public static void AsyncSendEmail(string recipient, string sender, string subject, string body, bool html) {
System.Threading.ThreadPool.QueueUserWorkItem(delegate(object state) {
MailMessage message = new MailMessage(sender, recipient, subject, body);
message.IsBodyHtml = html;
TrySendMessage(message);
});
}
/// <summary>
/// Tries to send a message, swallowing all exceptions.
/// </summary>
/// <param name="message">The message to send.</param>
private static void TrySendMessage(MailMessage message) {
try {
GenerateSmtpClient().Send(message);
}
catch(Exception ex) {
if(ex is SmtpException) {
Log.LogEntry("Unable to send Email: " + ex.Message, EntryType.Error, Log.SystemUsername);
}
else Log.LogEntry(ex.ToString(), EntryType.Error, Log.SystemUsername);
}
}
/// <summary>
/// Generates a new SMTP client with the proper settings.
/// </summary>
/// <returns>The generates SMTP client.</returns>
private static SmtpClient GenerateSmtpClient() {
SmtpClient client = new SmtpClient(Settings.SmtpServer);
if(Settings.SmtpUsername.Length > 0) {
client.Credentials = new NetworkCredential(Settings.SmtpUsername, Settings.SmtpPassword);
}
client.EnableSsl = Settings.SmtpSsl;
if(Settings.SmtpPort != -1) client.Port = Settings.SmtpPort;
else if(Settings.SmtpSsl) client.Port = 465;
return client;
}
/// <summary>
/// Gets the email addresses of a set of users.
/// </summary>
/// <param name="users">The users.</param>
/// <returns>The email addresses.</returns>
public static string[] GetRecipients(UserInfo[] users) {
if(users == null) return new string[0];
string[] result = new string[users.Length];
for(int i = 0; i < result.Length; i++) {
result[i] = users[i].Email;
}
return result;
}
/// <summary>
/// Asynchronously sends a mass email, using BCC.
/// </summary>
/// <param name="recipients">The recipents.</param>
/// <param name="sender">The sender.</param>
/// <param name="subject">The subject.</param>
/// <param name="body">The body.</param>
/// <param name="html"><c>true</c> if the body is HTML.</param>
public static void AsyncSendMassEmail(string[] recipients, string sender, string subject, string body, bool html) {
if(recipients.Length == 0) return;
System.Threading.ThreadPool.QueueUserWorkItem(delegate(object state) {
MailMessage message = new MailMessage(new MailAddress(sender), new MailAddress(sender));
message.Subject = subject;
message.Body = body;
for(int i = 0; i < recipients.Length; i++) {
message.Bcc.Add(new MailAddress(recipients[i]));
}
message.IsBodyHtml = html;
TrySendMessage(message);
});
}
/// <summary>
/// Notifies an error to the email addresses set in the configuration, swallowing all exceptions.
/// </summary>
/// <param name="ex">The exception to notify.</param>
/// <param name="url">The URL that caused the error, if any.</param>
public static void NotifyError(Exception ex, string url) {
try {
string[] recipients = Settings.ErrorsEmails;
if(recipients.Length > 0) {
AsyncSendMassEmail(recipients, Settings.SenderEmail, "Error Notification", "An error occurred on " +
DateTime.Now.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss") + " (server time) in the wiki hosted at " +
Settings.MainUrl + " - server stack trace follows.\r\n\r\n" +
(!string.IsNullOrEmpty(url) ? url + "\r\n\r\n" : "") +
ex.ToString(), false);
}
}
catch { }
}
}
}

113
Core/FileDocument.cs Normal file
View file

@ -0,0 +1,113 @@

using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.SearchEngine;
namespace ScrewTurn.Wiki {
/// <summary>
/// Represents a file document.
/// </summary>
public class FileDocument : IDocument {
/// <summary>
/// The type tag for a <see cref="T:FileDocument" />.
/// </summary>
public const string StandardTypeTag = "F";
private uint id;
private string name;
private string title;
private string typeTag = StandardTypeTag;
private DateTime dateTime;
private string provider;
/// <summary>
/// Initializes a new instance of the <see cref="T:FileDocument" /> class.
/// </summary>
/// <param name="fullName">The file full name.</param>
/// <param name="provider">The file provider.</param>
/// <param name="dateTime">The modification date/time.</param>
public FileDocument(string fullName, string provider, DateTime dateTime) {
if(fullName == null) throw new ArgumentNullException("fullName");
if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");
if(provider == null) throw new ArgumentNullException("provider");
if(provider.Length == 0) throw new ArgumentException("Provider cannot be empty", "provider");
id = Tools.HashDocumentNameForTemporaryIndex(fullName);
name = provider + "|" + fullName;
title = fullName.Substring(Tools.GetDirectoryName(fullName).Length);
this.dateTime = dateTime;
this.provider = provider;
}
/// <summary>
/// Initializes a new instance of the <see cref="T:FileDocument" /> class.
/// </summary>
/// <param name="doc">The dumped document.</param>
public FileDocument(DumpedDocument doc) {
string[] fields = doc.Name.Split('|');
id = doc.ID;
name = doc.Name;
title = doc.Title;
dateTime = doc.DateTime;
provider = fields[0];
}
/// <summary>
/// Gets or sets the globally unique ID of the document.
/// </summary>
public uint ID {
get { return id; }
set { id = value; }
}
/// <summary>
/// Gets the globally-unique name of the document.
/// </summary>
public string Name {
get { return name; }
}
/// <summary>
/// Gets the title of the document, if any.
/// </summary>
public string Title {
get { return title; }
}
/// <summary>
/// Gets the tag for the document type.
/// </summary>
public string TypeTag {
get { return typeTag; }
}
/// <summary>
/// Gets the document date/time.
/// </summary>
public DateTime DateTime {
get { return dateTime; }
}
/// <summary>
/// Performs the tokenization of the document content.
/// </summary>
/// <param name="content">The content to tokenize.</param>
/// <returns>The extracted words and their positions (always an empty array).</returns>
public WordInfo[] Tokenize(string content) {
return ScrewTurn.Wiki.SearchEngine.Tools.Tokenize(content);
}
/// <summary>
/// Gets the provider.
/// </summary>
public string Provider {
get { return provider; }
}
}
}

294
Core/FilesAndAttachments.cs Normal file
View file

@ -0,0 +1,294 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using System.IO;
namespace ScrewTurn.Wiki {
/// <summary>
/// Manages files, directories and attachments.
/// </summary>
public static class FilesAndAttachments {
#region Files
/// <summary>
/// Finds the provider that has a file.
/// </summary>
/// <param name="fullName">The full name of the file.</param>
/// <returns>The provider that has the file, or <c>null</c> if the file could not be found.</returns>
public static IFilesStorageProviderV30 FindFileProvider(string fullName) {
if(fullName == null) throw new ArgumentNullException("fullName");
if(string.IsNullOrEmpty(fullName)) throw new ArgumentException("Full Name cannot be empty", "fullName");
fullName = NormalizeFullName(fullName);
foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) {
FileDetails details = provider.GetFileDetails(fullName);
if(details != null) return provider;
}
return null;
}
/// <summary>
/// Gets the details of a file.
/// </summary>
/// <param name="fullName">The full name of the file.</param>
/// <returns>The details of the file, or <c>null</c> if no file is found.</returns>
public static FileDetails GetFileDetails(string fullName) {
if(fullName == null) throw new ArgumentNullException("fullName");
if(string.IsNullOrEmpty(fullName)) throw new ArgumentException("Full Name cannot be empty", "fullName");
fullName = NormalizeFullName(fullName);
foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) {
FileDetails details = provider.GetFileDetails(fullName);
if(details != null) return details;
}
return null;
}
/// <summary>
/// Retrieves a File.
/// </summary>
/// <param name="fullName">The full name of the File.</param>
/// <param name="output">The output stream.</param>
/// <param name="countHit">A value indicating whether or not to count this retrieval in the statistics.</param>
/// <returns><c>true</c> if the file is retrieved, <c>false</c> otherwise.</returns>
public static bool RetrieveFile(string fullName, Stream output, bool countHit) {
if(fullName == null) throw new ArgumentNullException("fullName");
if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");
if(output == null) throw new ArgumentNullException("destinationStream");
if(!output.CanWrite) throw new ArgumentException("Cannot write into Destination Stream", "destinationStream");
fullName = NormalizeFullName(fullName);
IFilesStorageProviderV30 provider = FindFileProvider(fullName);
if(provider == null) return false;
else return provider.RetrieveFile(fullName, output, countHit);
}
#endregion
#region Directories
/// <summary>
/// Finds the provider that has a directory.
/// </summary>
/// <param name="fullPath">The full path of the directory.</param>
/// <returns>The provider that has the directory, or <c>null</c> if no directory is found.</returns>
public static IFilesStorageProviderV30 FindDirectoryProvider(string fullPath) {
if(fullPath == null) throw new ArgumentNullException("fullPath");
if(fullPath.Length == 0) throw new ArgumentException("Full Path cannot be empty");
fullPath = NormalizeFullPath(fullPath);
// In order to verify that the full path exists, it is necessary to navigate
// from the root down to the specified directory level
// Example: /my/very/nested/directory/structure/
// 1. Check that / contains /my/
// 2. Check that /my/ contains /my/very/
// 3. ...
// allLevels contains this:
// /my/very/nested/directory/structure/
// /my/very/nested/directory/
// /my/very/nested/
// /my/very/
// /my/
// /
string oneLevelUp = fullPath;
List<string> allLevels = new List<string>(10);
allLevels.Add(fullPath.ToLowerInvariant());
while(oneLevelUp != "/") {
oneLevelUp = UpOneLevel(oneLevelUp);
allLevels.Add(oneLevelUp.ToLowerInvariant());
}
foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) {
bool allLevelsFound = true;
for(int i = allLevels.Count - 1; i >= 1; i--) {
string[] dirs = provider.ListDirectories(allLevels[i]);
string nextLevel =
(from d in dirs
where d.ToLowerInvariant() == allLevels[i - 1]
select d).FirstOrDefault();
if(string.IsNullOrEmpty(nextLevel)) {
allLevelsFound = false;
break;
}
}
if(allLevelsFound) return provider;
}
return null;
}
/// <summary>
/// Lists the directories in a directory.
/// </summary>
/// <param name="fullPath">The full path.</param>
/// <returns>The directories.</returns>
/// <remarks>If the specified directory is the root, then the list is performed on all providers.</remarks>
public static string[] ListDirectories(string fullPath) {
fullPath = NormalizeFullPath(fullPath);
if(fullPath == "/") {
List<string> directories = new List<string>(50);
foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) {
directories.AddRange(provider.ListDirectories(fullPath));
}
directories.Sort();
return directories.ToArray();
}
else {
IFilesStorageProviderV30 provider = FindDirectoryProvider(fullPath);
return provider.ListDirectories(fullPath);
}
}
/// <summary>
/// Lists the files in a directory.
/// </summary>
/// <param name="fullPath">The full path.</param>
/// <returns>The files.</returns>
/// <remarks>If the specified directory is the root, then the list is performed on all providers.</remarks>
public static string[] ListFiles(string fullPath) {
fullPath = NormalizeFullPath(fullPath);
if(fullPath == "/") {
List<string> files = new List<string>(50);
foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) {
files.AddRange(provider.ListFiles(fullPath));
}
files.Sort();
return files.ToArray();
}
else {
IFilesStorageProviderV30 provider = FindDirectoryProvider(fullPath);
return provider.ListFiles(fullPath);
}
}
#endregion
#region Page Attachments
/// <summary>
/// Finds the provider that has a page attachment.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="attachmentName">The name of the attachment.</param>
/// <returns>The provider that has the attachment, or <c>null</c> if the attachment could not be found.</returns>
public static IFilesStorageProviderV30 FindPageAttachmentProvider(PageInfo page, string attachmentName) {
if(page == null) throw new ArgumentNullException("page");
if(attachmentName == null) throw new ArgumentNullException("attachmentName");
if(attachmentName.Length == 0) throw new ArgumentException("Attachment Name cannot be empty", "attachmentName");
foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) {
FileDetails details = provider.GetPageAttachmentDetails(page, attachmentName);
if(details != null) return provider;
}
return null;
}
/// <summary>
/// Gets the details of a page attachment.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="attachmentName">The name of the attachment.</param>
/// <returns>The details of the attachment, or <c>null</c> if the attachment could not be found.</returns>
public static FileDetails GetPageAttachmentDetails(PageInfo page, string attachmentName) {
if(page == null) throw new ArgumentNullException("page");
if(attachmentName == null) throw new ArgumentNullException("attachmentName");
if(attachmentName.Length == 0) throw new ArgumentException("Attachment Name cannot be empty", "attachmentName");
foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) {
FileDetails details = provider.GetPageAttachmentDetails(page, attachmentName);
if(details != null) return details;
}
return null;
}
/// <summary>
/// Retrieves a Page Attachment.
/// </summary>
/// <param name="page">The Page Info that owns the Attachment.</param>
/// <param name="attachmentName">The name of the Attachment, for example "myfile.jpg".</param>
/// <param name="output">The output stream.</param>
/// <param name="countHit">A value indicating whether or not to count this retrieval in the statistics.</param>
/// <returns><c>true</c> if the Attachment is retrieved, <c>false</c> otherwise.</returns>
public static bool RetrievePageAttachment(PageInfo page, string attachmentName, Stream output, bool countHit) {
if(page == null) throw new ArgumentNullException("pageInfo");
if(attachmentName == null) throw new ArgumentNullException("name");
if(attachmentName.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(output == null) throw new ArgumentNullException("destinationStream");
if(!output.CanWrite) throw new ArgumentException("Cannot write into Destination Stream", "destinationStream");
IFilesStorageProviderV30 provider = FindPageAttachmentProvider(page, attachmentName);
if(provider == null) return false;
else return provider.RetrievePageAttachment(page, attachmentName, output, countHit);
}
#endregion
/// <summary>
/// Normalizes a full name.
/// </summary>
/// <param name="fullName">The full name.</param>
/// <returns>The normalized full name.</returns>
private static string NormalizeFullName(string fullName) {
if(!fullName.StartsWith("/")) fullName = "/" + fullName;
return fullName;
}
/// <summary>
/// Normalizes a full path.
/// </summary>
/// <param name="fullPath">The full path.</param>
/// <returns>The normalized full path.</returns>
private static string NormalizeFullPath(string fullPath) {
if(fullPath == null) return "/";
if(!fullPath.StartsWith("/")) fullPath = "/" + fullPath;
if(!fullPath.EndsWith("/")) fullPath += "/";
return fullPath;
}
/// <summary>
/// Goes up one level in a directory path.
/// </summary>
/// <param name="fullPath">The full path, normalized, different from "/".</param>
/// <returns>The directory.</returns>
private static string UpOneLevel(string fullPath) {
if(fullPath == "/") throw new ArgumentException("Cannot navigate up from the root");
string temp = fullPath.Trim('/');
int lastIndex = temp.LastIndexOf("/");
return "/" + temp.Substring(0, lastIndex + 1);
}
}
}

View file

@ -0,0 +1,989 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Implements a Local Files Storage Provider.
/// </summary>
public class FilesStorageProvider : IFilesStorageProviderV30 {
private readonly ComponentInformation info = new ComponentInformation("Local Files Provider",
"ScrewTurn Software", Settings.WikiVersion, "http://www.screwturn.eu", null);
// The following strings MUST terminate with DirectorySeparatorPath in order to properly work
// in BuildFullPath method
private readonly string UploadDirectory = "Upload" + Path.DirectorySeparatorChar;
private readonly string AttachmentsDirectory = "Attachments" + Path.DirectorySeparatorChar;
private const string FileDownloadsFile = "FileDownloads.cs";
private const string AttachmentDownloadsFile = "AttachmentDownloads.cs";
// 16 KB buffer used in the StreamCopy method
// 16 KB seems to be the best break-even between performance and memory usage
private const int BufferSize = 16384;
private IHostV30 host;
private string GetFullPath(string finalChunk) {
return Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), finalChunk);
}
/// <summary>
/// Initializes the Storage Provider.
/// </summary>
/// <param name="host">The Host of the Component.</param>
/// <param name="config">The Configuration data, if any.</param>
/// <exception cref="ArgumentNullException">If <paramref name="host"/> or <paramref name="config"/> are <c>null</c>.</exception>
/// <exception cref="InvalidConfigurationException">If <paramref name="config"/> is not valid or is incorrect.</exception>
public void Init(IHostV30 host, string config) {
if(host == null) throw new ArgumentNullException("host");
if(config == null) throw new ArgumentNullException("config");
this.host = host;
if(!LocalProvidersTools.CheckWritePermissions(host.GetSettingValue(SettingName.PublicDirectory))) {
throw new InvalidConfigurationException("Cannot write into the public directory - check permissions");
}
// Create directories, if needed
if(!Directory.Exists(GetFullPath(UploadDirectory))) {
Directory.CreateDirectory(GetFullPath(UploadDirectory));
}
if(!Directory.Exists(GetFullPath(AttachmentsDirectory))) {
Directory.CreateDirectory(GetFullPath(AttachmentsDirectory));
}
if(!File.Exists(GetFullPath(FileDownloadsFile))) {
File.Create(GetFullPath(FileDownloadsFile)).Close();
}
if(!File.Exists(GetFullPath(AttachmentDownloadsFile))) {
File.Create(GetFullPath(AttachmentDownloadsFile)).Close();
}
}
/// <summary>
/// Method invoked on shutdown.
/// </summary>
/// <remarks>This method might not be invoked in some cases.</remarks>
public void Shutdown() {
// Nothing to do
}
/// <summary>
/// Gets the Information about the Provider.
/// </summary>
public ComponentInformation Information {
get { return info; }
}
/// <summary>
/// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
/// </summary>
public string ConfigHelpHtml {
get { return null; }
}
/// <summary>
/// Gets a value specifying whether the provider is read-only, i.e. it can only provide data and not store it.
/// </summary>
public bool ReadOnly {
get { return false; }
}
/// <summary>
/// Builds a full path from a provider-specific partial path.
/// </summary>
/// <param name="partialPath">The partial path.</param>
/// <returns>The full path.</returns>
/// <remarks>For example: if <b>partialPath</b> is "/my/directory", the method returns
/// "C:\Inetpub\wwwroot\Wiki\public\Upload\my\directory", assuming the Wiki resides in "C:\Inetpub\wwwroot\Wiki".</remarks>
private string BuildFullPath(string partialPath) {
if(partialPath == null) partialPath = "";
partialPath = partialPath.Replace("/", Path.DirectorySeparatorChar.ToString()).TrimStart(Path.DirectorySeparatorChar);
string up = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), UploadDirectory);
return Path.Combine(up, partialPath); // partialPath CANNOT start with "\" -> Path.Combine does not work
}
/// <summary>
/// Builds a full path from a provider-specific partial path.
/// </summary>
/// <param name="partialPath">The partial path.</param>
/// <returns>The full path.</returns>
/// <remarks>For example: if <b>partialPath</b> is "/my/directory", the method returns
/// "C:\Inetpub\wwwroot\Wiki\public\Attachments\my\directory", assuming the Wiki resides in "C:\Inetpub\wwwroot\Wiki".</remarks>
private string BuildFullPathForAttachments(string partialPath) {
if(partialPath == null) partialPath = "";
partialPath = partialPath.Replace("/", Path.DirectorySeparatorChar.ToString()).TrimStart(Path.DirectorySeparatorChar);
string up = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), AttachmentsDirectory);
return Path.Combine(up, partialPath); // partialPath CANNOT start with "\" -> Path.Combine does not work
}
/// <summary>
/// Lists the Files in the specified Directory.
/// </summary>
/// <param name="directory">The full directory name, for example "/my/directory". Null, empty or "/" for the root directory.</param>
/// <returns>The list of Files in the directory.</returns>
/// <exception cref="ArgumentException">If <paramref name="directory"/> does not exist.</exception>
public string[] ListFiles(string directory) {
string d = BuildFullPath(directory);
if(!Directory.Exists(d)) throw new ArgumentException("Directory does not exist", "directory");
string[] temp = Directory.GetFiles(d);
// Result must be transformed in the form /my/dir/file.ext
List<string> res = new List<string>(temp.Length);
string root = GetFullPath(UploadDirectory);
foreach(string s in temp) {
// root = C:\blah\ - ends with '\'
res.Add(s.Substring(root.Length - 1).Replace(Path.DirectorySeparatorChar, '/'));
}
return res.ToArray();
}
/// <summary>
/// Lists the Directories in the specified directory.
/// </summary>
/// <param name="directory">The full directory name, for example "/my/directory". Null, empty or "/" for the root directory.</param>
/// <returns>The list of Directories in the Directory.</returns>
/// <exception cref="ArgumentException">If <paramref name="directory"/> does not exist.</exception>
public string[] ListDirectories(string directory) {
string d = BuildFullPath(directory);
if(!Directory.Exists(d)) throw new ArgumentException("Directory does not exist", "directory");
string[] temp = Directory.GetDirectories(d);
// Result must be transformed in the form /my/dir
List<string> res = new List<string>(temp.Length);
string root = GetFullPath(UploadDirectory);
foreach(string s in temp) {
// root = C:\blah\ - ends with '\'
res.Add(s.Substring(root.Length - 1).Replace(Path.DirectorySeparatorChar, '/') + "/");
}
return res.ToArray();
}
/// <summary>
/// Copies data from a Stream to another.
/// </summary>
/// <param name="source">The Source stream.</param>
/// <param name="destination">The destination Stream.</param>
private static void StreamCopy(Stream source, Stream destination) {
byte[] buff = new byte[BufferSize];
int copied = 0;
do {
copied = source.Read(buff, 0, buff.Length);
if(copied > 0) {
destination.Write(buff, 0, copied);
}
} while(copied > 0);
}
/// <summary>
/// Stores a file.
/// </summary>
/// <param name="fullName">The full name of the file.</param>
/// <param name="sourceStream">A Stream object used as <b>source</b> of a byte stream,
/// i.e. the method reads from the Stream and stores the content properly.</param>
/// <param name="overwrite"><c>true</c> to overwrite an existing file.</param>
/// <returns><c>true</c> if the File is stored, <c>false</c> otherwise.</returns>
/// <remarks>If <b>overwrite</b> is <c>false</c> and File already exists, the method returns <c>false</c>.</remarks>
/// <exception cref="ArgumentNullException">If <typeparamref name="fullName"/> os <paramref name="sourceStream"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="fullName"/> is empty or <paramref name="sourceStream"/> does not support reading.</exception>
public bool StoreFile(string fullName, Stream sourceStream, bool overwrite) {
if(fullName == null) throw new ArgumentNullException("fullName");
if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");
if(sourceStream == null) throw new ArgumentNullException("sourceStream");
if(!sourceStream.CanRead) throw new ArgumentException("Cannot read from Source Stream", "sourceStream");
string filename = BuildFullPath(fullName);
// Abort if the file already exists and overwrite is false
if(File.Exists(filename) && !overwrite) return false;
FileStream fs = null;
bool done = false;
try {
fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
// StreamCopy content (throws exception in case of error)
StreamCopy(sourceStream, fs);
done = true;
}
catch(IOException) {
done = false;
}
finally {
try {
fs.Close();
}
catch { }
}
return done;
}
/// <summary>
/// Retrieves a File.
/// </summary>
/// <param name="fullName">The full name of the File.</param>
/// <param name="destinationStream">A Stream object used as <b>destination</b> of a byte stream,
/// i.e. the method writes to the Stream the file content.</param>
/// <param name="countHit">A value indicating whether or not to count this retrieval in the statistics.</param>
/// <returns><c>true</c> if the file is retrieved, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <typeparamref name="fullName"/> os <paramref name="destinationStream"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="fullName"/> is empty or <paramref name="destinationStream"/> does not support writing, or if <paramref name="fullName"/> does not exist.</exception>
public bool RetrieveFile(string fullName, Stream destinationStream, bool countHit) {
if(fullName == null) throw new ArgumentNullException("fullName");
if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");
if(destinationStream == null) throw new ArgumentNullException("destinationStream");
if(!destinationStream.CanWrite) throw new ArgumentException("Cannot write into Destination Stream", "destinationStream");
string filename = BuildFullPath(fullName);
if(!File.Exists(filename)) throw new ArgumentException("File does not exist", "fullName");
FileStream fs = null;
bool done = false;
try {
fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
// StreamCopy content (throws exception in case of error)
StreamCopy(fs, destinationStream);
done = true;
}
catch(IOException) {
done = false;
}
finally {
try {
fs.Close();
}
catch { }
}
if(countHit) {
AddDownloadHit(fullName, GetFullPath(FileDownloadsFile));
}
return done;
}
/// <summary>
/// Adds a download hit for the specified item in the specified output file.
/// </summary>
/// <param name="itemName">The item.</param>
/// <param name="outputFile">The full path to the output file.</param>
private void AddDownloadHit(string itemName, string outputFile) {
lock(this) {
string[] lines = File.ReadAllLines(outputFile);
string lowercaseItemName = itemName.ToLowerInvariant();
string[] fields;
bool found = false;
for(int i = 0; i < lines.Length; i++) {
fields = lines[i].Split('|');
if(fields[0].ToLowerInvariant() == lowercaseItemName) {
int count = 0;
int.TryParse(fields[1], out count);
count = count + 1;
lines[i] = itemName + "|" + count.ToString();
found = true;
}
}
if(!found) {
// Add a new line for the current item
string[] newLines = new string[lines.Length + 1];
Array.Copy(lines, 0, newLines, 0, lines.Length);
newLines[newLines.Length - 1] = itemName + "|1";
lines = newLines;
}
// Overwrite file with updated data
File.WriteAllLines(outputFile, lines);
}
}
/// <summary>
/// Sets the download hits for the specified item in the specified file.
/// </summary>
/// <param name="itemName">The item.</param>
/// <param name="outputFile">The full path of the output file.</param>
/// <param name="count">The hit count to set.</param>
private void SetDownloadHits(string itemName, string outputFile, int count) {
lock(this) {
string[] lines = File.ReadAllLines(outputFile);
List<string> outputLines = new List<string>(lines.Length);
string lowercaseItemName = itemName.ToLowerInvariant();
string[] fields;
foreach(string line in lines) {
fields = line.Split('|');
if(fields[0].ToLowerInvariant() == lowercaseItemName) {
// Set the new count
outputLines.Add(fields[0] + "|" + count.ToString());
}
else {
// Copy data with no modification
outputLines.Add(line);
}
}
File.WriteAllLines(outputFile, outputLines.ToArray());
}
}
/// <summary>
/// Clears the download hits for the items that match <b>itemName</b> in the specified file.
/// </summary>
/// <param name="itemName">The first part of the item name.</param>
/// <param name="outputFile">The full path of the output file.</param>
private void ClearDownloadHitsPartialMatch(string itemName, string outputFile) {
lock(this) {
string[] lines = File.ReadAllLines(outputFile);
List<string> newLines = new List<string>(lines.Length);
string lowercaseItemName = itemName.ToLowerInvariant();
string[] fields;
foreach(string line in lines) {
fields = line.Split('|');
if(!fields[0].ToLowerInvariant().StartsWith(lowercaseItemName)) {
newLines.Add(line);
}
}
File.WriteAllLines(outputFile, newLines.ToArray());
}
}
/// <summary>
/// Renames an item of the download count list in the specified file.
/// </summary>
/// <param name="oldItemName">The old item name.</param>
/// <param name="newItemName">The new item name.</param>
/// <param name="outputFile">The full path of the output file.</param>
private void RenameDownloadHitsItem(string oldItemName, string newItemName, string outputFile) {
lock(this) {
string[] lines = File.ReadAllLines(outputFile);
string lowercaseOldItemName = oldItemName.ToLowerInvariant();
string[] fields;
bool found = false;
for(int i = 0; i < lines.Length; i++) {
fields = lines[i].Split('|');
if(fields[0].ToLowerInvariant() == lowercaseOldItemName) {
lines[i] = newItemName + "|" + fields[1];
found = true;
break;
}
}
if(found) {
File.WriteAllLines(outputFile, lines);
}
}
}
/// <summary>
/// Renames an item of the download count list in the specified file.
/// </summary>
/// <param name="oldItemName">The initial part of the old item name.</param>
/// <param name="newItemName">The corresponding initial part of the new item name.</param>
/// <param name="outputFile">The full path of the output file.</param>
private void RenameDownloadHitsItemPartialMatch(string oldItemName, string newItemName, string outputFile) {
lock(this) {
string[] lines = File.ReadAllLines(outputFile);
string lowercaseOldItemName = oldItemName.ToLowerInvariant();
string[] fields;
bool found = false;
for(int i = 0; i < lines.Length; i++) {
fields = lines[i].Split('|');
if(fields[0].ToLowerInvariant().StartsWith(lowercaseOldItemName)) {
lines[i] = newItemName + fields[0].Substring(lowercaseOldItemName.Length) + "|" + fields[1];
found = true;
}
}
if(found) {
File.WriteAllLines(outputFile, lines);
}
}
}
/// <summary>
/// Gets the number of times a file was retrieved.
/// </summary>
/// <param name="fullName">The full name of the file.</param>
/// <returns>The number of times the file was retrieved.</returns>
private int GetFileRetrievalCount(string fullName) {
if(fullName == null) throw new ArgumentNullException("fullName");
if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");
lock(this) {
// Format
// /Full/Path/To/File.txt|DownloadCount
string[] lines = File.ReadAllLines(GetFullPath(FileDownloadsFile));
string lowercaseFullName = fullName.ToLowerInvariant();
string[] fields;
foreach(string line in lines) {
fields = line.Split('|');
if(fields[0].ToLowerInvariant() == lowercaseFullName) {
int res = 0;
if(int.TryParse(fields[1], out res)) return res;
else return 0;
}
}
}
return 0;
}
/// <summary>
/// Clears the number of times a file was retrieved.
/// </summary>
/// <param name="fullName">The full name of the file.</param>
/// <param name="count">The count to set.</param>
/// <exception cref="ArgumentNullException">If <paramref name="fullName"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="fullName"/> is empty.</exception>
/// <exception cref="ArgumentOutOfRangeException">If <paramref name="count"/> is less than zero.</exception>
public void SetFileRetrievalCount(string fullName, int count) {
if(fullName == null) throw new ArgumentNullException("fullName");
if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");
if(count < 0) throw new ArgumentOutOfRangeException("count", "Count must be greater than or equal to zero");
SetDownloadHits(fullName, GetFullPath(FileDownloadsFile), 0);
}
/// <summary>
/// Gets the details of a file.
/// </summary>
/// <param name="fullName">The full name of the file.</param>
/// <returns>The details, or <c>null</c> if the file does not exist.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="fullName"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="fullName"/> is empty.</exception>
public FileDetails GetFileDetails(string fullName) {
if(fullName == null) throw new ArgumentNullException("fullName");
if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");
string n = BuildFullPath(fullName);
if(!File.Exists(n)) return null;
FileInfo fi = new FileInfo(n);
return new FileDetails(fi.Length, fi.LastWriteTime, GetFileRetrievalCount(fullName));
}
/// <summary>
/// Deletes a File.
/// </summary>
/// <param name="fullName">The full name of the File.</param>
/// <returns><c>true</c> if the File is deleted, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="fullName"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="fullName"/> is empty or it does not exist.</exception>
public bool DeleteFile(string fullName) {
if(fullName == null) throw new ArgumentNullException("fullName");
if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName");
string n = BuildFullPath(fullName);
if(!File.Exists(n)) throw new ArgumentException("File does not exist", "fullName");
try {
File.Delete(n);
SetDownloadHits(fullName, GetFullPath(FileDownloadsFile), 0);
return true;
}
catch(IOException) {
return false;
}
}
/// <summary>
/// Renames or moves a File.
/// </summary>
/// <param name="oldFullName">The old full name of the File.</param>
/// <param name="newFullName">The new full name of the File.</param>
/// <returns><c>true</c> if the File is renamed, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="oldFullName"/> or <paramref name="newFullName"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="oldFullName"/> or <paramref name="newFullName"/> are empty, or if the old file does not exist, or if the new file already exist.</exception>
public bool RenameFile(string oldFullName, string newFullName) {
if(oldFullName == null) throw new ArgumentNullException("oldFullName");
if(oldFullName.Length == 0) throw new ArgumentException("Old Full Name cannot be empty", "oldFullName");
if(newFullName == null) throw new ArgumentNullException("newFullName");
if(newFullName.Length == 0) throw new ArgumentException("New Full Name cannot be empty", "newFullName");
string oldFilename = BuildFullPath(oldFullName);
string newFilename = BuildFullPath(newFullName);
if(!File.Exists(oldFilename)) throw new ArgumentException("Old File does not exist", "oldFullName");
if(File.Exists(newFilename)) throw new ArgumentException("New File already exists", "newFullName");
try {
File.Move(oldFilename, newFilename);
RenameDownloadHitsItem(oldFullName, newFullName, GetFullPath(FileDownloadsFile));
return true;
}
catch(IOException) {
return false;
}
}
/// <summary>
/// Creates a new Directory.
/// </summary>
/// <param name="path">The path to create the new Directory in.</param>
/// <param name="name">The name of the new Directory.</param>
/// <returns><c>true</c> if the Directory is created, <c>false</c> otherwise.</returns>
/// <remarks>If <b>path</b> is "/my/directory" and <b>name</b> is "newdir", a new directory named "/my/directory/newdir" is created.</remarks>
/// <exception cref="ArgumentNullException">If <paramref name="path"/> or <paramref name="name"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="name"/> is empty or if the directory does not exist, or if the new directory already exists.</exception>
public bool CreateDirectory(string path, string name) {
if(path == null) throw new ArgumentNullException("path");
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(!Directory.Exists(BuildFullPath(path))) throw new ArgumentException("Directory does not exist", "path");
string partialPath = path + (!path.EndsWith("/") ? "/" : "") + name;
string d = BuildFullPath(partialPath);
if(Directory.Exists(d)) throw new ArgumentException("Directory already exists", "name");
try {
Directory.CreateDirectory(d);
return true;
}
catch(IOException) {
return false;
}
}
/// <summary>
/// Deletes a Directory and <b>all of its content</b>.
/// </summary>
/// <param name="fullPath">The full path of the Directory.</param>
/// <returns><c>true</c> if the Directory is delete, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="fullPath"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="fullPath"/> is empty or if it equals '/' or it does not exist.</exception>
public bool DeleteDirectory(string fullPath) {
if(fullPath == null) throw new ArgumentNullException("fullPath");
if(fullPath.Length == 0) throw new ArgumentException("Full Path cannot be empty", "fullPath");
if(fullPath == "/") throw new ArgumentException("Cannot delete the root directory", "fullPath");
string d = BuildFullPath(fullPath);
if(!Directory.Exists(d)) throw new ArgumentException("Directory does not exist", "fullPath");
try {
Directory.Delete(d, true);
// Make sure tht fullPath ends with "/" so that the method does not clear wrong items
if(!fullPath.EndsWith("/")) fullPath += "/";
ClearDownloadHitsPartialMatch(fullPath, GetFullPath(FileDownloadsFile));
return true;
}
catch(IOException) {
return false;
}
}
/// <summary>
/// Renames or moves a Directory.
/// </summary>
/// <param name="oldFullPath">The old full path of the Directory.</param>
/// <param name="newFullPath">The new full path of the Directory.</param>
/// <returns><c>true</c> if the Directory is renamed, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="oldFullPath"/> or <paramref name="newFullPath"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="oldFullPath"/> or <paramref name="newFullPath"/> are empty or equal to '/',
/// or if the old directory does not exist or the new directory already exists.</exception>
public bool RenameDirectory(string oldFullPath, string newFullPath) {
if(oldFullPath == null) throw new ArgumentNullException("oldFullPath");
if(oldFullPath.Length == 0) throw new ArgumentException("Old Full Path cannot be empty", "oldFullPath");
if(oldFullPath == "/") throw new ArgumentException("Cannot rename the root directory", "oldFullPath");
if(newFullPath == null) throw new ArgumentNullException("newFullPath");
if(newFullPath.Length == 0) throw new ArgumentException("New Full Path cannot be empty", "newFullPath");
if(newFullPath == "/") throw new ArgumentException("Cannot rename directory to the root directory", "newFullPath");
string olddir = BuildFullPath(oldFullPath);
string newdir = BuildFullPath(newFullPath);
if(!Directory.Exists(olddir)) throw new ArgumentException("Directory does not exist", "oldFullPath");
if(Directory.Exists(newdir)) throw new ArgumentException("Directory already exists", "newFullPath");
try {
Directory.Move(olddir, newdir);
// Make sure that oldFullPath and newFullPath end with "/" so that the method does not rename wrong items
if(!oldFullPath.EndsWith("/")) oldFullPath += "/";
if(!newFullPath.EndsWith("/")) newFullPath += "/";
RenameDownloadHitsItemPartialMatch(oldFullPath, newFullPath, GetFullPath(FileDownloadsFile));
return true;
}
catch(IOException) {
return false;
}
}
/// <summary>
/// Gets the name of the Directory containing the Attachments of a Page.
/// </summary>
/// <param name="pageInfo">The Page Info.</param>
/// <returns>The name of the Directory (not the full path) that contains the Attachments of the specified Page.</returns>
private string GetPageAttachmentDirectory(PageInfo pageInfo) {
// Use the Hash to avoid problems with special chars and the like
// Using the hash prevents GetPageWithAttachments to work
//return Hash.Compute(pageInfo.FullName);
return pageInfo.FullName;
}
/// <summary>
/// The the names of the pages with attachments.
/// </summary>
/// <returns>The names of the pages with attachments.</returns>
public string[] GetPagesWithAttachments() {
string[] directories = Directory.GetDirectories(GetFullPath(AttachmentsDirectory));
string[] result = new string[directories.Length];
for(int i = 0; i < result.Length; i++) {
result[i] = Path.GetFileName(directories[i]);
}
return result;
}
/// <summary>
/// Returns the names of the Attachments of a Page.
/// </summary>
/// <param name="pageInfo">The Page Info object that owns the Attachments.</param>
/// <returns>The names, or an empty list.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="pageInfo"/> is <c>null</c>.</exception>
public string[] ListPageAttachments(PageInfo pageInfo) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
string dir = BuildFullPathForAttachments(GetPageAttachmentDirectory(pageInfo));
if(!Directory.Exists(dir)) return new string[0];
string[] files = Directory.GetFiles(dir);
// Result must contain only the filename, not the full path
List<string> result = new List<string>(files.Length);
foreach(string f in files) {
result.Add(Path.GetFileName(f));
}
return result.ToArray();
}
/// <summary>
/// Stores a Page Attachment.
/// </summary>
/// <param name="pageInfo">The Page Info that owns the Attachment.</param>
/// <param name="name">The name of the Attachment, for example "myfile.jpg".</param>
/// <param name="sourceStream">A Stream object used as <b>source</b> of a byte stream,
/// i.e. the method reads from the Stream and stores the content properly.</param>
/// <param name="overwrite"><c>true</c> to overwrite an existing Attachment.</param>
/// <returns><c>true</c> if the Attachment is stored, <c>false</c> otherwise.</returns>
/// <remarks>If <b>overwrite</b> is <c>false</c> and Attachment already exists, the method returns <c>false</c>.</remarks>
/// <exception cref="ArgumentNullException">If <paramref name="pageInfo"/>, <paramref name="name"/> or <paramref name="sourceStream"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="name"/> is empty or if <paramref name="sourceStream"/> does not support reading.</exception>
public bool StorePageAttachment(PageInfo pageInfo, string name, Stream sourceStream, bool overwrite) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(sourceStream == null) throw new ArgumentNullException("sourceStream");
if(!sourceStream.CanRead) throw new ArgumentException("Cannot read from Source Stream", "sourceStream");
string filename = BuildFullPathForAttachments(GetPageAttachmentDirectory(pageInfo) + "/" + name);
if(!Directory.Exists(Path.GetDirectoryName(filename))) {
try {
Directory.CreateDirectory(Path.GetDirectoryName(filename));
}
catch(IOException) {
// Cannot create attachments dir
return false;
}
}
if(File.Exists(filename) && !overwrite) return false;
FileStream fs = null;
bool done = false;
try {
fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
// StreamCopy content (throws exception in case of error)
StreamCopy(sourceStream, fs);
done = true;
}
catch(IOException) {
return false;
}
finally {
try {
fs.Close();
}
catch { }
}
return done;
}
/// <summary>
/// Retrieves a Page Attachment.
/// </summary>
/// <param name="pageInfo">The Page Info that owns the Attachment.</param>
/// <param name="name">The name of the Attachment, for example "myfile.jpg".</param>
/// <param name="destinationStream">A Stream object used as <b>destination</b> of a byte stream,
/// i.e. the method writes to the Stream the file content.</param>
/// <param name="countHit">A value indicating whether or not to count this retrieval in the statistics.</param>
/// <returns><c>true</c> if the Attachment is retrieved, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="pageInfo"/>, <paramref name="name"/> or <paramref name="destinationStream"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="name"/> is empty or if <paramref name="destinationStream"/> does not support writing,
/// or if the page does not have attachments or if the attachment does not exist.</exception>
public bool RetrievePageAttachment(PageInfo pageInfo, string name, Stream destinationStream, bool countHit) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(destinationStream == null) throw new ArgumentNullException("destinationStream");
if(!destinationStream.CanWrite) throw new ArgumentException("Cannot write into Destination Stream", "destinationStream");
string d = GetPageAttachmentDirectory(pageInfo);
if(!Directory.Exists(BuildFullPathForAttachments(d))) throw new ArgumentException("No attachments for Page", "pageInfo");
string filename = BuildFullPathForAttachments(d + "/" + name);
if(!File.Exists(filename)) throw new ArgumentException("Attachment does not exist", "name");
FileStream fs = null;
bool done = false;
try {
fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
// StreamCopy content (throws exception in case of error)
StreamCopy(fs, destinationStream);
done = true;
}
catch(IOException) {
done = false;
}
finally {
try {
fs.Close();
}
catch { }
}
if(countHit) {
AddDownloadHit(pageInfo.FullName + "." + name, GetFullPath(AttachmentDownloadsFile));
}
return done;
}
/// <summary>
/// Gets the number of times a page attachment was retrieved.
/// </summary>
/// <param name="pageInfo">The page.</param>
/// <param name="name">The name of the attachment.</param>
/// <returns>The number of times the attachment was retrieved.</returns>
private int GetPageAttachmentRetrievalCount(PageInfo pageInfo, string name) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
lock(this) {
// Format
// PageName.File|DownloadCount
string[] lines = File.ReadAllLines(GetFullPath(AttachmentDownloadsFile));
string lowercaseFullName = pageInfo.FullName + "." + name;
lowercaseFullName = lowercaseFullName.ToLowerInvariant();
string[] fields;
foreach(string line in lines) {
fields = line.Split('|');
if(fields[0].ToLowerInvariant() == lowercaseFullName) {
int count;
if(int.TryParse(fields[1], out count)) return count;
else return 0;
}
}
}
return 0;
}
/// <summary>
/// Set the number of times a page attachment was retrieved.
/// </summary>
/// <param name="pageInfo">The page.</param>
/// <param name="name">The name of the attachment.</param>
/// <param name="count">The count to set.</param>
/// <exception cref="ArgumentNullException">If <paramref name="pageInfo"/> or <paramref name="name"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="name"/> is empty.</exception>
/// <exception cref="ArgumentOutOfRangeException">If <paramref name="count"/> is less than zero.</exception>
public void SetPageAttachmentRetrievalCount(PageInfo pageInfo, string name, int count) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty");
if(count < 0) throw new ArgumentOutOfRangeException("Count must be greater than or equal to zero", "count");
SetDownloadHits(pageInfo.FullName + "." + name, GetFullPath(AttachmentDownloadsFile), count);
}
/// <summary>
/// Gets the details of a page attachment.
/// </summary>
/// <param name="pageInfo">The page that owns the attachment.</param>
/// <param name="name">The name of the attachment, for example "myfile.jpg".</param>
/// <returns>The details of the attachment, or <c>null</c> if the attachment does not exist.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="pageInfo"/> or <paramref name="name"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="name"/> is empty.</exception>
public FileDetails GetPageAttachmentDetails(PageInfo pageInfo, string name) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty");
string d = GetPageAttachmentDirectory(pageInfo);
if(!Directory.Exists(BuildFullPathForAttachments(d))) return null;
string filename = BuildFullPathForAttachments(d + "/" + name);
if(!File.Exists(filename)) return null;
FileInfo fi = new FileInfo(filename);
return new FileDetails(fi.Length, fi.LastWriteTime, GetPageAttachmentRetrievalCount(pageInfo, name));
}
/// <summary>
/// Deletes a Page Attachment.
/// </summary>
/// <param name="pageInfo">The Page Info that owns the Attachment.</param>
/// <param name="name">The name of the Attachment, for example "myfile.jpg".</param>
/// <returns><c>true</c> if the Attachment is deleted, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="pageInfo"/> or <paramref name="name"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="name"/> is empty or if the page or attachment do not exist.</exception>
public bool DeletePageAttachment(PageInfo pageInfo, string name) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty");
string d = GetPageAttachmentDirectory(pageInfo);
if(!Directory.Exists(BuildFullPathForAttachments(d))) throw new ArgumentException("Page does not exist", "pageInfo");
string filename = BuildFullPathForAttachments(d + "/" + name);
if(!File.Exists(filename)) throw new ArgumentException("Attachment does not exist", "name");
try {
File.Delete(filename);
SetDownloadHits(pageInfo.FullName + "." + name, GetFullPath(AttachmentDownloadsFile), 0);
return true;
}
catch(IOException) {
return false;
}
}
/// <summary>
/// Renames a Page Attachment.
/// </summary>
/// <param name="pageInfo">The Page Info that owns the Attachment.</param>
/// <param name="oldName">The old name of the Attachment.</param>
/// <param name="newName">The new name of the Attachment.</param>
/// <returns><c>true</c> if the Attachment is renamed, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="pageInfo"/>, <paramref name="oldName"/> or <paramref name="newName"/> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <paramref name="pageInfo"/>, <paramref name="oldName"/> or <paramref name="newName"/> are empty,
/// or if the page or old attachment do not exist, or the new attachment name already exists.</exception>
public bool RenamePageAttachment(PageInfo pageInfo, string oldName, string newName) {
if(pageInfo == null) throw new ArgumentNullException("pageInfo");
if(oldName == null) throw new ArgumentNullException("oldName");
if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName");
if(newName == null) throw new ArgumentNullException("newName");
if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName");
string d = GetPageAttachmentDirectory(pageInfo);
if(!Directory.Exists(BuildFullPathForAttachments(d))) throw new ArgumentException("Page does not exist", "pageInfo");
string oldFilename = BuildFullPathForAttachments(d + "/" + oldName);
if(!File.Exists(oldFilename)) throw new ArgumentException("Attachment does not exist", "oldName");
string newFilename = BuildFullPathForAttachments(d + "/" + newName);
if(File.Exists(newFilename)) throw new ArgumentException("Attachment already exists", "newName");
try {
File.Move(oldFilename, newFilename);
RenameDownloadHitsItem(pageInfo.FullName + "." + oldName, pageInfo.FullName + "." + newName,
GetFullPath(AttachmentDownloadsFile));
return true;
}
catch(IOException) {
return false;
}
}
/// <summary>
/// Notifies to the Provider that a Page has been renamed.
/// </summary>
/// <param name="oldPage">The old Page Info object.</param>
/// <param name="newPage">The new Page Info object.</param>
/// <exception cref="ArgumentNullException">If <paramref name="oldPage"/> or <paramref name="newPage"/> are <c>null</c></exception>
/// <exception cref="ArgumentException">If the new page is already in use.</exception>
public void NotifyPageRenaming(PageInfo oldPage, PageInfo newPage) {
if(oldPage == null) throw new ArgumentNullException("oldPage");
if(newPage == null) throw new ArgumentNullException("newPage");
string oldName = GetPageAttachmentDirectory(oldPage);
string newName = GetPageAttachmentDirectory(newPage);
string oldDir = BuildFullPathForAttachments(oldName);
string newDir = BuildFullPathForAttachments(newName);
if(!Directory.Exists(oldDir)) return; // Nothing to do
if(Directory.Exists(newDir)) throw new ArgumentException("New Page already exists", "newPage");
try {
Directory.Move(oldDir, newDir);
RenameDownloadHitsItemPartialMatch(oldPage.FullName + ".", newPage.FullName + ".",
GetFullPath(AttachmentDownloadsFile));
}
catch(IOException) { }
}
}
}

2611
Core/Formatter.cs Normal file

File diff suppressed because it is too large Load diff

155
Core/FormattingPipeline.cs Normal file
View file

@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using ScrewTurn.Wiki.PluginFramework;
using System.Web;
namespace ScrewTurn.Wiki {
/// <summary>
/// Contains methods for formatting content using a pipeline paradigm.
/// </summary>
public static class FormattingPipeline {
/// <summary>
/// Gets the formatter providers list sorted by priority.
/// </summary>
/// <returns>The list.</returns>
private static IList<IFormatterProviderV30> GetSortedFormatters() {
List<IFormatterProviderV30> providers = new List<IFormatterProviderV30>(Collectors.FormatterProviderCollector.AllProviders);
// Sort by priority, then by name
providers.Sort((x, y) => {
int preliminaryResult = x.ExecutionPriority.CompareTo(y.ExecutionPriority);
if(preliminaryResult != 0) return preliminaryResult;
else return x.Information.Name.CompareTo(y.Information.Name);
});
return providers;
}
/// <summary>
/// Performs the Phases 1 and 2 of the formatting process.
/// </summary>
/// <param name="raw">The raw WikiMarkup to format.</param>
/// <param name="forIndexing">A value indicating whether the formatting is being done for content indexing.</param>
/// <param name="context">The formatting context.</param>
/// <param name="current">The current Page, if any.</param>
/// <returns>The formatted content.</returns>
public static string FormatWithPhase1And2(string raw, bool forIndexing, FormattingContext context, PageInfo current) {
string[] tempLinks;
return FormatWithPhase1And2(raw, forIndexing, context, current, out tempLinks);
}
/// <summary>
/// Performs the Phases 1 and 2 of the formatting process.
/// </summary>
/// <param name="raw">The raw WikiMarkup to format.</param>
/// <param name="forIndexing">A value indicating whether the formatting is being done for content indexing.</param>
/// <param name="context">The formatting context.</param>
/// <param name="current">The current Page, if any.</param>
/// <param name="linkedPages">The Pages linked by the current Page.</param>
/// <returns>The formatted content.</returns>
public static string FormatWithPhase1And2(string raw, bool forIndexing, FormattingContext context, PageInfo current, out string[] linkedPages) {
ContextInformation info = null;
string username = SessionFacade.CurrentUsername;
info = new ContextInformation(forIndexing, false, context, current, System.Threading.Thread.CurrentThread.CurrentCulture.Name, HttpContext.Current,
username, SessionFacade.GetCurrentGroupNames());
IList<IFormatterProviderV30> providers = GetSortedFormatters();
// Phase 1
foreach(IFormatterProviderV30 provider in providers) {
if(provider.PerformPhase1) {
try {
raw = provider.Format(raw, info, FormattingPhase.Phase1);
}
catch(Exception ex) {
Log.LogEntry("Provider " + provider.Information.Name + " failed to perform Phase1 (silently resuming from next provider): " + ex.ToString(), EntryType.Error, Log.SystemUsername);
}
}
}
raw = Formatter.Format(raw, forIndexing, context, current, out linkedPages);
// Phase 2
foreach(IFormatterProviderV30 provider in providers) {
if(provider.PerformPhase2) {
try {
raw = provider.Format(raw, info, FormattingPhase.Phase2);
}
catch(Exception ex) {
Log.LogEntry("Provider " + provider.Information.Name + " failed to perform Phase2 (silently resuming from next provider): " + ex.ToString(), EntryType.Error, Log.SystemUsername);
}
}
}
return raw;
}
/// <summary>
/// Performs the Phase 3 of the formatting process.
/// </summary>
/// <param name="raw">The raw WikiMarkup to format.</param>
/// <param name="context">The formatting context.</param>
/// <param name="current">The current Page, if any.</param>
/// <returns>The formatted content.</returns>
public static string FormatWithPhase3(string raw, FormattingContext context, PageInfo current) {
raw = Formatter.FormatPhase3(raw, context, current);
ContextInformation info = null;
string username = SessionFacade.CurrentUsername;
info = new ContextInformation(false, false, context, current, System.Threading.Thread.CurrentThread.CurrentCulture.Name, HttpContext.Current,
username, SessionFacade.GetCurrentGroupNames());
// Phase 3
foreach(IFormatterProviderV30 provider in GetSortedFormatters()) {
if(provider.PerformPhase3) {
try {
raw = provider.Format(raw, info, FormattingPhase.Phase3);
}
catch(Exception ex) {
Log.LogEntry("Provider " + provider.Information.Name + " failed to perform Phase3 (silently resuming from next provider): " + ex.ToString(), EntryType.Error, Log.SystemUsername);
}
}
}
return raw;
}
/// <summary>
/// Prepares the title of an item for display.
/// </summary>
/// <param name="title">The input title.</param>
/// <param name="forIndexing">A value indicating whether the formatting is being done for content indexing.</param>
/// <param name="context">The context information.</param>
/// <param name="current">The current page, if any.</param>
/// <returns>The prepared title, properly sanitized.</returns>
public static string PrepareTitle(string title, bool forIndexing, FormattingContext context, PageInfo current) {
string temp = title;
ContextInformation info = new ContextInformation(forIndexing, false, context, current, System.Threading.Thread.CurrentThread.CurrentCulture.Name,
HttpContext.Current, SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames());
foreach(IFormatterProviderV30 prov in GetSortedFormatters()) {
temp = prov.PrepareTitle(temp, info);
}
return PrepareItemTitle(temp);
}
/// <summary>
/// Prepares the title of an item for safe display.
/// </summary>
/// <param name="title">The title.</param>
/// <returns>The sanitized title.</returns>
private static string PrepareItemTitle(string title) {
return Formatter.StripHtml(title)
.Replace("\"", "&quot;")
.Replace("'", "&#39;")
.Replace("<", "&lt;").Replace(">", "&gt;")
.Replace("[", "&#91;").Replace("]", "&#93;"); // This avoid endless loops in Formatter
}
}
}

55
Core/Hash.cs Normal file
View file

@ -0,0 +1,55 @@
using System;
using System.Configuration;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Web.Security;
namespace ScrewTurn.Wiki {
/// <summary>
/// Helps computing Hash codes.
/// </summary>
public static class Hash {
/// <summary>
/// Computes the Hash code of a string.
/// </summary>
/// <param name="input">The string.</param>
/// <returns>The Hash code.</returns>
public static byte[] ComputeBytes(string input) {
MD5 md5 = MD5CryptoServiceProvider.Create();
return md5.ComputeHash(Encoding.ASCII.GetBytes(input));
}
/// <summary>
/// Computes the Hash code of a string and converts it into a Hex string.
/// </summary>
/// <param name="input">The string.</param>
/// <returns>The Hash code, converted into a Hex string.</returns>
public static string Compute(string input) {
byte[] bytes = ComputeBytes(input);
string result = "";
for(int i = 0; i < bytes.Length; i++) {
result += string.Format("{0:X2}", bytes[i]);
}
return result;
}
/// <summary>
/// Computes the Hash of a Username, mixing it with other data, in order to avoid illegal Account activations.
/// </summary>
/// <param name="username">The Username.</param>
/// <param name="email">The email.</param>
/// <param name="dateTime">The date/time.</param>
/// <param name="otherData">The other data to mix into the input string.</param>
/// <returns>The secured Hash of the Username.</returns>
public static string ComputeSecurityHash(string username, string email, DateTime dateTime, string otherData) {
// Use a salt (is this actually useful given that STW is opensource and everyone can see the salt?)
return Compute(otherData + username + email + dateTime.ToString("yyyyMMddHHmmss") +"$kbfl?nfc4");
}
}
}

1005
Core/Host.cs Normal file

File diff suppressed because it is too large Load diff

20
Core/ITranslator.cs Normal file
View file

@ -0,0 +1,20 @@

using System;
namespace ScrewTurn.Wiki.ImportWiki {
/// <summary>
/// Exposes an interface for building import tools.
/// </summary>
public interface ITranslator {
/// <summary>
/// Executes the translation.
/// </summary>
/// <param name="input">The input content.</param>
/// <returns>The WikiMarkup.</returns>
string Translate(string input);
}
}

491
Core/IndexStorer.cs Normal file
View file

@ -0,0 +1,491 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using ScrewTurn.Wiki.SearchEngine;
namespace ScrewTurn.Wiki {
/// <summary>
/// Stores index data to disk.
/// </summary>
/// <remarks>Instance and static members are <b>thread-safe</b>.</remarks>
public class IndexStorer : IndexStorerBase {
private static readonly byte[] ReservedBytes = new byte[] { 2, 0, 0, 0, 0, 0, 0, 0 };
private static readonly int Zero = 0;
private string documentsFile, wordsFile, mappingsFile;
private uint firstFreeDocumentId = 1;
private uint firstFreeWordId = 1;
// Documents file binary format
// Reserved(8bytes) Count(int) Entries...
// ID(int) Name(string) Title(string) TypeTag(string) DateTime(long)
// Words file binary format
// Reserved(8bytes) Count(int) Entries...
// ID(int) Text(string)
// Mappings file binary format
// Reserved(8bytes) Count(int) Entries...
// WordID(int) DocumentID(int) FirstCharIndex(int) WordIndex(int) Location(int)
/// <summary>
/// Initializes a new instance of the <see cref="IndexStorer" /> class.
/// </summary>
/// <param name="documentsFile">The file that contains the documents list.</param>
/// <param name="wordsFile">The file that contains the words list.</param>
/// <param name="mappingsFile">The file that contains the index mappings data.</param>
/// <param name="index">The index to manage.</param>
public IndexStorer(string documentsFile, string wordsFile, string mappingsFile, IInMemoryIndex index)
: base(index) {
if(documentsFile == null) throw new ArgumentNullException("documentsFile");
if(wordsFile == null) throw new ArgumentNullException("wordsFile");
if(mappingsFile == null) throw new ArgumentNullException("mappingsFile");
if(documentsFile.Length == 0) throw new ArgumentException("Documents File cannot be empty", "documentsFile");
if(wordsFile.Length == 0) throw new ArgumentException("Words File cannot be emtpy", "wordsFile");
if(mappingsFile.Length == 0) throw new ArgumentException("Mappings File cannot be empty", "mappingsFile");
this.documentsFile = documentsFile;
this.wordsFile = wordsFile;
this.mappingsFile = mappingsFile;
InitFiles();
}
/// <summary>
/// Gets the approximate size, in bytes, of the search engine index.
/// </summary>
public override long Size {
get {
lock(this) {
long size = 0;
FileInfo fi;
fi = new FileInfo(documentsFile);
size += fi.Length;
fi = new FileInfo(wordsFile);
size += fi.Length;
fi = new FileInfo(mappingsFile);
size += fi.Length;
return size;
}
}
}
/// <summary>
/// Loads the index from the data store the first time.
/// </summary>
/// <param name="documents">The dumped documents.</param>
/// <param name="words">The dumped words.</param>
/// <param name="mappings">The dumped word mappings.</param>
protected override void LoadIndexInternal(out DumpedDocument[] documents, out DumpedWord[] words, out DumpedWordMapping[] mappings) {
uint maxDocumentId = 0;
uint maxWordId = 0;
// 1. Load Documents
using(FileStream fs = new FileStream(documentsFile, FileMode.Open, FileAccess.Read, FileShare.None)) {
int count = ReadCount(fs);
BinaryReader reader = new BinaryReader(fs, Encoding.UTF8);
documents = new DumpedDocument[count];
for(int i = 0; i < count; i++) {
documents[i] = ReadDumpedDocument(reader);
if(documents[i].ID > maxDocumentId) maxDocumentId = documents[i].ID;
}
firstFreeDocumentId = maxDocumentId + 1;
}
// 2. Load Words
using(FileStream fs = new FileStream(wordsFile, FileMode.Open, FileAccess.Read, FileShare.None)) {
int count = ReadCount(fs);
BinaryReader reader = new BinaryReader(fs, Encoding.UTF8);
words = new DumpedWord[count];
for(int i = 0; i < count; i++) {
words[i] = ReadDumpedWord(reader);
if(words[i].ID > maxWordId) maxWordId = words[i].ID;
}
firstFreeWordId = maxWordId + 1;
}
// 3. Load Mappings
using(FileStream fs = new FileStream(mappingsFile, FileMode.Open, FileAccess.Read, FileShare.None)) {
int count = ReadCount(fs);
BinaryReader reader = new BinaryReader(fs, Encoding.UTF8);
mappings = new DumpedWordMapping[count];
for(int i = 0; i < count; i++) {
mappings[i] = ReadDumpedWordMapping(reader);
}
}
}
/// <summary>
/// Reads the reserved bytes.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader" /> to read from.</param>
/// <returns><c>true</c> if read bytes are equal to expected bytes, <c>false</c> otherwise.</returns>
private static bool ReadReserved(BinaryReader reader) {
bool allEqual = true;
for(int i = 0; i < ReservedBytes.Length; i++) {
int r = reader.ReadByte();
if(r != ReservedBytes[i]) allEqual = false;
}
return allEqual;
}
/// <summary>
/// Initializes the data files, if needed.
/// </summary>
private void InitFiles() {
if(!File.Exists(documentsFile)) {
using(FileStream fs = new FileStream(documentsFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8);
WriteHeader(writer);
}
}
if(!File.Exists(wordsFile)) {
using(FileStream fs = new FileStream(wordsFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8);
WriteHeader(writer);
}
}
if(!File.Exists(mappingsFile)) {
using(FileStream fs = new FileStream(mappingsFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8);
WriteHeader(writer);
}
}
}
/// <summary>
/// Initializes the data storage.
/// </summary>
/// <param name="state">A state object passed from the index.</param>
protected override void InitDataStore(object state) {
using(FileStream fs = new FileStream(documentsFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8);
WriteHeader(writer);
}
using(FileStream fs = new FileStream(wordsFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8);
WriteHeader(writer);
}
using(FileStream fs = new FileStream(mappingsFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8);
WriteHeader(writer);
}
}
/// <summary>
/// Writes the binary file header.
/// </summary>
/// <param name="writer">The <see cref="BinaryWriter" /> to write into.</param>
private static void WriteHeader(BinaryWriter writer) {
writer.Write(ReservedBytes);
writer.Write(Zero);
}
/// <summary>
/// Reads a <see cref="DumpedDocument" /> from a <see cref="BinaryReader" />.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader" />.</param>
/// <returns>The <see cref="DumpedDocument" />.</returns>
private static DumpedDocument ReadDumpedDocument(BinaryReader reader) {
uint id;
string name, title, typeTag;
DateTime dateTime;
id = reader.ReadUInt32();
name = reader.ReadString();
title = reader.ReadString();
typeTag = reader.ReadString();
dateTime = DateTime.FromBinary(reader.ReadInt64());
return new DumpedDocument(id, name, title, typeTag, dateTime);
}
/// <summary>
/// Reads a <see cref="DumpedWord" /> from a <see cref="BinaryReader" />.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader" />.</param>
/// <returns>The <see cref="DumpedWord" />.</returns>
private static DumpedWord ReadDumpedWord(BinaryReader reader) {
uint id;
string text;
id = reader.ReadUInt32();
text = reader.ReadString();
return new DumpedWord(id, text);
}
/// <summary>
/// Reads a <see cref="DumpedWordMapping" /> from a <see cref="BinaryReader" />.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader" />.</param>
/// <returns>The <see cref="DumpedWordMapping" />.</returns>
private static DumpedWordMapping ReadDumpedWordMapping(BinaryReader reader) {
uint wordId;
uint documentId;
ushort firstCharIndex, wordIndex;
byte location;
wordId = reader.ReadUInt32();
documentId = reader.ReadUInt32();
firstCharIndex = reader.ReadUInt16();
wordIndex = reader.ReadUInt16();
location = reader.ReadByte();
return new DumpedWordMapping(wordId, documentId, firstCharIndex, wordIndex, location);
}
/// <summary>
/// Reads the count in a <see cref="FileStream" />.
/// </summary>
/// <param name="fs">The <see cref="FileStream" />, at position <b>zero</b>.</param>
/// <returns>The count.</returns>
/// <remarks>The caller must properly seek the stream after calling the method.</remarks>
private static int ReadCount(FileStream fs) {
BinaryReader reader = new BinaryReader(fs, Encoding.UTF8);
if(!ReadReserved(reader)) {
throw new InvalidOperationException("Invalid index file header");
}
return reader.ReadInt32();
}
/// <summary>
/// Stores new data into the data storage.
/// </summary>
/// <param name="data">The data to store.</param>
/// <param name="state">A state object passed from the index.</param>
/// <returns>The storer result, if any.</returns>
/// <remarks>When saving a new document, the document ID in data.Mappings must be
/// replaced with the currect document ID, generated by the concrete implementation of
/// this method. data.Words should have IDs numbered from uint.MaxValue downwards.
/// The method re-numbers the words appropriately.</remarks>
protected override IndexStorerResult SaveData(DumpedChange data, object state) {
IndexStorerResult result = new IndexStorerResult(null, null);
// 1. Save Document
using(FileStream fs = new FileStream(documentsFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) {
int count = ReadCount(fs);
// Update count and append document
BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8);
fs.Seek(-4, SeekOrigin.Current);
writer.Write(count + 1);
writer.Seek(0, SeekOrigin.End);
data.Document.ID = firstFreeDocumentId;
WriteDumpedDocument(writer, data.Document);
result.DocumentID = firstFreeDocumentId;
firstFreeDocumentId++;
}
// 2. Save Words
Dictionary<uint, WordId> wordIds = null;
using(FileStream fs = new FileStream(wordsFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) {
int count = ReadCount(fs);
// Update count and append words
BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8);
fs.Seek(-4, SeekOrigin.Current);
writer.Write(count + data.Words.Count);
fs.Seek(0, SeekOrigin.End);
wordIds = new Dictionary<uint, WordId>(data.Words.Count);
foreach(DumpedWord dw in data.Words) {
wordIds.Add(dw.ID, new WordId(dw.Text, firstFreeWordId));
dw.ID = firstFreeWordId;
WriteDumpedWord(writer, dw);
firstFreeWordId++;
}
result.WordIDs = new List<WordId>(wordIds.Values);
}
// 3. Save Mappings
using(FileStream fs = new FileStream(mappingsFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) {
int count = ReadCount(fs);
// Update count and append mappings
BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8);
fs.Seek(-4, SeekOrigin.Current);
writer.Write(count + data.Mappings.Count);
fs.Seek(0, SeekOrigin.End);
foreach(DumpedWordMapping map in data.Mappings) {
// Words are autonumbered from uint.MaxValue downwards by IndexBase so that
// IndexStorer can identify the DumpedWordMappings easily and
// fix the IDs with the ones actually stored
WordId newMappingWordId;
if(wordIds != null && wordIds.TryGetValue(map.WordID, out newMappingWordId)) {
map.WordID = newMappingWordId.ID;
}
WriteDumpedWordMapping(writer,
new DumpedWordMapping(map.WordID, result.DocumentID.Value,
map.FirstCharIndex, map.WordIndex, map.Location));
}
}
return result;
}
/// <summary>
/// Gets a tempDumpedWord file name given an original name.
/// </summary>
/// <param name="file">The original name.</param>
/// <returns>The tempDumpedWord file name.</returns>
private static string GetTempFile(string file) {
string folder = Path.GetDirectoryName(file);
string name = Path.GetFileNameWithoutExtension(file) + "_Temp" + Path.GetExtension(file);
return Path.Combine(folder, name);
}
/// <summary>
/// Deletes data from the data storage.
/// </summary>
/// <param name="data">The data to delete.</param>
/// <param name="state">A state object passed from the index.</param>
protected override void DeleteData(DumpedChange data, object state) {
// Files are regenerated in a tempDumpedWord location and copied back
string tempDocumentsFile = GetTempFile(documentsFile);
string tempWordsFile = GetTempFile(wordsFile);
string tempMappingsFile = GetTempFile(mappingsFile);
// 1. Remove Mappings
using(FileStream fsi = new FileStream(mappingsFile, FileMode.Open, FileAccess.Read, FileShare.None)) {
int count = ReadCount(fsi);
int countLocation = (int)fsi.Position - 4;
int writeCount = 0;
BinaryReader reader = new BinaryReader(fsi, Encoding.UTF8);
using(FileStream fso = new FileStream(tempMappingsFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
BinaryWriter writer = new BinaryWriter(fso, Encoding.UTF8);
WriteHeader(writer);
DumpedWordMapping m;
for(int i = 0; i < count; i++) {
m = ReadDumpedWordMapping(reader);
// If m is not contained in data.Mappings, store it in tempDumpedWord file
if(!Find(m, data.Mappings)) {
WriteDumpedWordMapping(writer, m);
writeCount++;
}
}
writer.Seek(countLocation, SeekOrigin.Begin);
writer.Write(writeCount);
}
}
// Replace the file
File.Copy(tempMappingsFile, mappingsFile, true);
File.Delete(tempMappingsFile);
// 2. Remove Words
using(FileStream fsi = new FileStream(wordsFile, FileMode.Open, FileAccess.Read, FileShare.None)) {
int count = ReadCount(fsi);
int countLocation = (int)fsi.Position - 4;
int writeCount = 0;
BinaryReader reader = new BinaryReader(fsi, Encoding.UTF8);
using(FileStream fso = new FileStream(tempWordsFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
BinaryWriter writer = new BinaryWriter(fso, Encoding.UTF8);
WriteHeader(writer);
DumpedWord w;
for(int i = 0; i < count; i++) {
w = ReadDumpedWord(reader);
// If w is not contained in data.Words, store it in tempDumpedWord file
if(!Find(w, data.Words)) {
WriteDumpedWord(writer, w);
writeCount++;
}
}
writer.Seek(countLocation, SeekOrigin.Begin);
writer.Write(writeCount);
}
}
// Replace the file
File.Copy(tempWordsFile, wordsFile, true);
File.Delete(tempWordsFile);
// 3. Remove Document
using(FileStream fsi = new FileStream(documentsFile, FileMode.Open, FileAccess.Read, FileShare.None)) {
int count = ReadCount(fsi);
int countLocation = (int)fsi.Position - 4;
BinaryReader reader = new BinaryReader(fsi, Encoding.UTF8);
using(FileStream fso = new FileStream(tempDocumentsFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
BinaryWriter writer = new BinaryWriter(fso, Encoding.UTF8);
WriteHeader(writer);
DumpedDocument d;
for(int i = 0; i < count; i++) {
d = ReadDumpedDocument(reader);
// If d is not equal to data.Document (to be deleted), then copy it to the result file
if(!EqualDumpedDocument(d, data.Document)) {
WriteDumpedDocument(writer, d);
}
}
writer.Seek(countLocation, SeekOrigin.Begin);
writer.Write(count - 1);
}
}
File.Copy(tempDocumentsFile, documentsFile, true);
File.Delete(tempDocumentsFile);
}
/// <summary>
/// Writes a <see cref="DumpedDocument" /> to a <see cref="BinaryWriter" />.
/// </summary>
/// <param name="writer">The <see cref="BinaryWriter" />.</param>
/// <param name="document">The <see cref="DumpedDocument" />.</param>
private static void WriteDumpedDocument(BinaryWriter writer, DumpedDocument document) {
writer.Write(document.ID);
writer.Write(document.Name);
writer.Write(document.Title);
writer.Write(document.TypeTag);
writer.Write(document.DateTime.ToBinary());
}
/// <summary>
/// Writes a <see cref="DumpedWord" /> to a <see cref="BinaryWriter" />.
/// </summary>
/// <param name="writer">The <see cref="BinaryWriter" />.</param>
/// <param name="word">The <see cref="DumpedWord" />.</param>
private static void WriteDumpedWord(BinaryWriter writer, DumpedWord word) {
//if(word.Text.Length == 0) throw new InvalidOperationException();
writer.Write(word.ID);
writer.Write(word.Text);
}
/// <summary>
/// Writes a <see cref="DumpedWordMapping" /> to a <see cref="BinaryWriter" />.
/// </summary>
/// <param name="writer">The <see cref="BinaryWriter" />.</param>
/// <param name="mapping">The <see cref="DumpedWordMapping" />.</param>
private static void WriteDumpedWordMapping(BinaryWriter writer, DumpedWordMapping mapping) {
writer.Write(mapping.WordID);
writer.Write(mapping.DocumentID);
writer.Write(mapping.FirstCharIndex);
writer.Write(mapping.WordIndex);
writer.Write(mapping.Location);
}
/// <summary>
/// Determines whether two <see cref="DumpedDocument" />s are equal.
/// </summary>
/// <param name="d1">The first document.</param>
/// <param name="d2">The second document.</param>
/// <returns><c>true</c> if the documents are equal, <c>false</c> otherwise.</returns>
private static bool EqualDumpedDocument(DumpedDocument d1, DumpedDocument d2) {
// Only consider ID, Name and TypeTag
//return d1.ID == d2.ID && d1.Name == d2.Name && d1.Title == d2.Title &&
// d1.TypeTag == d2.TypeTag && d1.DateTime == d2.DateTime;
return d1.ID == d2.ID && d1.Name == d2.Name && d1.TypeTag == d2.TypeTag;
}
}
}

39
Core/LocalPageInfo.cs Normal file
View file

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Represents a Local Page.
/// </summary>
public class LocalPageInfo : PageInfo {
private string file;
/// <summary>
/// Initializes a new instance of the <b>PageInfo</b> class.
/// </summary>
/// <param name="fullName">The Full Name of the Page.</param>
/// <param name="provider">The Pages Storage Provider that manages this Page.</param>
/// <param name="creationDateTime">The creation Date/Time.</param>
/// <param name="file">The relative path of the file used for data storage.</param>
public LocalPageInfo(string fullName, IPagesStorageProviderV30 provider, DateTime creationDateTime, string file)
: base(fullName, provider, creationDateTime) {
this.file = file;
}
/// <summary>
/// Gets or sets the relative path of the File used for data storage.
/// </summary>
public string File {
get { return file; }
set { file = value; }
}
}
}

View file

@ -0,0 +1,48 @@

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.Configuration;
namespace ScrewTurn.Wiki {
/// <summary>
/// Implements tools for local providers.
/// </summary>
public static class LocalProvidersTools {
/// <summary>
/// Checks a directory for write permissions.
/// </summary>
/// <param name="dir">The directory.</param>
/// <returns><c>true</c> if the directory has write permissions, <c>false</c> otherwise.</returns>
public static bool CheckWritePermissions(string dir) {
string file = System.IO.Path.Combine(dir, "__StwTestFile.txt");
bool canWrite = true;
System.IO.FileStream fs = null;
try {
fs = System.IO.File.Create(file);
fs.Write(Encoding.ASCII.GetBytes("Hello"), 0, 5);
}
catch {
canWrite = false;
}
finally {
try {
if(fs != null) fs.Close();
System.IO.File.Delete(file);
}
catch {
canWrite = false;
}
}
return canWrite;
}
}
}

42
Core/LocalUserInfo.cs Normal file
View file

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Stores a Local UserInfo object.
/// </summary>
public class LocalUserInfo : UserInfo {
private string passwordHash;
/// <summary>
/// Initializes a new instance of the <b>LocalUserInfo</b> class.
/// </summary>
/// <param name="username">The Username.</param>
/// <param name="displayName">The display name.</param>
/// <param name="email">The Email.</param>
/// <param name="active">Specifies whether the Account is active or not.</param>
/// <param name="dateTime">The creation DateTime.</param>
/// <param name="provider">The Users Storage Provider that manages the User.</param>
/// <param name="passwordHash">The Password Hash.</param>
public LocalUserInfo(string username, string displayName, string email, bool active, DateTime dateTime,
IUsersStorageProviderV30 provider, string passwordHash)
: base(username, displayName, email, active, dateTime, provider) {
this.passwordHash = passwordHash;
}
/// <summary>
/// Gets or sets the Password Hash.
/// </summary>
public string PasswordHash {
get { return passwordHash; }
set { passwordHash = value; }
}
}
}

57
Core/Log.cs Normal file
View file

@ -0,0 +1,57 @@
using System;
using System.Configuration;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Web;
using System.Web.Security;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Records and retrieves Log Entries.
/// </summary>
public static class Log {
/// <summary>
/// The system username ('SYSTEM').
/// </summary>
public const string SystemUsername = "SYSTEM";
/// <summary>
/// Writes an Entry in the Log.
/// </summary>
/// <param name="message">The Message.</param>
/// <param name="type">The Type of the Entry.</param>
/// <param name="user">The User that generated the Entry.</param>
public static void LogEntry(string message, EntryType type, string user) {
try {
Settings.Provider.LogEntry(message, type, user);
}
catch { }
}
/// <summary>
/// Reads all the Log Entries (newest to oldest).
/// </summary>
/// <returns>The Entries.</returns>
public static List<LogEntry> ReadEntries() {
List<LogEntry> entries = new List<LogEntry>(Settings.Provider.GetLogEntries());
entries.Reverse();
return entries;
}
/// <summary>
/// Clears the Log.
/// </summary>
public static void ClearLog() {
Settings.Provider.ClearLog();
}
}
}

90
Core/MimeTypes.cs Normal file
View file

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
namespace ScrewTurn.Wiki {
/// <summary>
/// Contains a list of MIME Types.
/// </summary>
public static class MimeTypes {
private static Dictionary<string, string> types;
/// <summary>
/// Initializes the list of the MIME Types, with the most common media types.
/// </summary>
public static void Init() {
types = new Dictionary<string, string>(100);
// Images
types.Add("jpg", "image/jpeg");
types.Add("jpeg", "image/jpeg");
types.Add("jpe", "image/jpeg");
types.Add("gif", "image/gif");
types.Add("png", "image/png");
types.Add("bmp", "image/bmp");
types.Add("tif", "image/tiff");
types.Add("tiff", "image/tiff");
types.Add("svg", "image/svg+xml");
types.Add("ico", "image/x-icon");
// Text
types.Add("txt", "text/plain");
types.Add("htm", "text/html");
types.Add("html", "text/html");
types.Add("xhtml", "text/xhtml");
types.Add("xml", "text/xml");
types.Add("xsl", "text/xsl");
types.Add("dtd", "application/xml-dtd");
types.Add("css", "text/css");
types.Add("rtf", "text/rtf");
// Archives
types.Add("zip", "application/zip");
types.Add("tar", "application/x-tar");
// Multimedia
types.Add("ogg", "application/ogg");
types.Add("swf", "application/x-shockwave-flash");
types.Add("mpga", "audio/mpeg");
types.Add("mp2", "audio/mpeg");
types.Add("mp3", "audio/mpeg");
types.Add("m3u", "audio/x-mpegurl");
types.Add("ram", "audio/x-pn-realaudio");
types.Add("ra", "audio/x-pn-realaudio");
types.Add("rm", "application/vnd.rn-realmedia");
types.Add("wav", "application/x-wav");
types.Add("mpg", "video/mpeg");
types.Add("mpeg", "video/mpeg");
types.Add("mpe", "video/mpeg");
types.Add("mov", "video/quicktime");
types.Add("qt", "video/quicktime");
types.Add("avi", "video/x-msvideo");
// Office
types.Add("doc", "application/msword");
types.Add("xls", "application/vnd.ms-excel");
types.Add("ppt", "application/vnd.ms-powerpoint");
// Map Office 2007 formats using the information found here:
// http://www.therightstuff.de/2006/12/16/Office+2007+File+Icons+For+Windows+SharePoint+Services+20+And+SharePoint+Portal+Server+2003.aspx
types.Add("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
types.Add("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
types.Add("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
// Other
types.Add("pdf", "application/pdf");
types.Add("ai", "application/postscript");
types.Add("ps", "application/postscript");
types.Add("eps", "application/postscript");
}
/// <summary>
/// Gets the list of the MIME Types.
/// </summary>
public static Dictionary<string, string> Types {
get { return types; }
}
}
}

148
Core/NavigationPaths.cs Normal file
View file

@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Manages navigation paths.
/// </summary>
public static class NavigationPaths {
/// <summary>
/// Gets the list of the Navigation Paths.
/// </summary>
/// <returns>The navigation paths, sorted by name.</returns>
public static List<NavigationPath> GetAllNavigationPaths() {
List<NavigationPath> allPaths = new List<NavigationPath>(30);
// Retrieve paths from every Pages provider
foreach(IPagesStorageProviderV30 provider in Collectors.PagesProviderCollector.AllProviders) {
allPaths.AddRange(provider.GetNavigationPaths(null));
foreach(NamespaceInfo nspace in provider.GetNamespaces()) {
allPaths.AddRange(provider.GetNavigationPaths(nspace));
}
}
allPaths.Sort(new NavigationPathComparer());
return allPaths;
}
/// <summary>
/// Gets the list of the Navigation Paths in a namespace.
/// </summary>
/// <param name="nspace">The namespace.</param>
/// <returns>The navigation paths, sorted by name.</returns>
public static List<NavigationPath> GetNavigationPaths(NamespaceInfo nspace) {
List<NavigationPath> allPaths = new List<NavigationPath>(30);
// Retrieve paths from every Pages provider
foreach(IPagesStorageProviderV30 provider in Collectors.PagesProviderCollector.AllProviders) {
allPaths.AddRange(provider.GetNavigationPaths(nspace));
}
allPaths.Sort(new NavigationPathComparer());
return allPaths;
}
/// <summary>
/// Finds a Navigation Path's Name.
/// </summary>
/// <param name="name">The Name.</param>
/// <returns>True if the Navigation Path exists.</returns>
public static bool Exists(string name) {
return Find(name) != null;
}
/// <summary>
/// Finds and returns a Path.
/// </summary>
/// <param name="fullName">The full name.</param>
/// <returns>The correct <see cref="T:NavigationPath" /> object or <c>null</c> if no path is found.</returns>
public static NavigationPath Find(string fullName) {
List<NavigationPath> allPaths = GetAllNavigationPaths();
int idx = allPaths.BinarySearch(new NavigationPath(fullName, null), new NavigationPathComparer());
if(idx >= 0) return allPaths[idx];
else return null;
}
/// <summary>
/// Adds a new Navigation Path.
/// </summary>
/// <param name="nspace">The target namespace (<c>null</c> for the root).</param>
/// <param name="name">The Name.</param>
/// <param name="pages">The Pages.</param>
/// <param name="provider">The Provider to use for the new Navigation Path, or <c>null</c> for the default provider.</param>
/// <returns>True if the Path is added successfully.</returns>
public static bool AddNavigationPath(NamespaceInfo nspace, string name, List<PageInfo> pages, IPagesStorageProviderV30 provider) {
string namespaceName = nspace != null ? nspace.Name : null;
string fullName = NameTools.GetFullName(namespaceName, name);
if(Exists(fullName)) return false;
if(provider == null) provider = Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider);
NavigationPath newPath = provider.AddNavigationPath(namespaceName, name, pages.ToArray());
if(newPath != null) Log.LogEntry("Navigation Path " + fullName + " added", EntryType.General, Log.SystemUsername);
else Log.LogEntry("Creation failed for Navigation Path " + fullName, EntryType.Error, Log.SystemUsername);
return newPath != null;
}
/// <summary>
/// Removes a Navigation Path.
/// </summary>
/// <param name="fullName">The full name of the path to remove.</param>
/// <returns><c>true</c> if the path is removed, <c>false</c> otherwise.</returns>
public static bool RemoveNavigationPath(string fullName) {
NavigationPath path = Find(fullName);
if(path == null) return false;
bool done = path.Provider.RemoveNavigationPath(path);
if(done) Log.LogEntry("Navigation Path " + fullName + " removed", EntryType.General, Log.SystemUsername);
else Log.LogEntry("Deletion failed for Navigation Path " + fullName, EntryType.Error, Log.SystemUsername);
return done;
}
/// <summary>
/// Modifies a Navigation Path.
/// </summary>
/// <param name="fullName">The full name of the path to modify.</param>
/// <param name="pages">The list of Pages.</param>
/// <returns><c>true</c> if the path is modified, <c>false</c> otherwise.</returns>
public static bool ModifyNavigationPath(string fullName, List<PageInfo> pages) {
NavigationPath path = Find(fullName);
if(path == null) return false;
NavigationPath newPath = path.Provider.ModifyNavigationPath(path, pages.ToArray());
if(newPath != null) Log.LogEntry("Navigation Path " + fullName + " modified", EntryType.General, Log.SystemUsername);
else Log.LogEntry("Modification failed for Navigation Path " + fullName, EntryType.Error, Log.SystemUsername);
return newPath != null;
}
/// <summary>
/// Finds all the Navigation Paths that include a Page.
/// </summary>
/// <param name="page">The Page.</param>
/// <returns>The list of Navigation Paths.</returns>
public static string[] PathsPerPage(PageInfo page) {
NamespaceInfo pageNamespace = Pages.FindNamespace(NameTools.GetNamespace(page.FullName));
List<string> result = new List<string>(10);
List<NavigationPath> allPaths = GetNavigationPaths(pageNamespace);
for(int i = 0; i < allPaths.Count; i++) {
List<string> pages = new List<string>(allPaths[i].Pages);
if(pages.Contains(page.FullName)) {
result.Add(allPaths[i].FullName);
}
}
return result.ToArray();
}
}
}

View file

@ -0,0 +1,126 @@

using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.SearchEngine;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Represents a page attachment document.
/// </summary>
public class PageAttachmentDocument : IDocument {
/// <summary>
/// The type tag for a <see cref="T:PageAttachmentDocument" />.
/// </summary>
public const string StandardTypeTag = "A";
private uint id;
private string name;
private string title;
private string typeTag = StandardTypeTag;
private DateTime dateTime;
private PageInfo page;
private string provider;
/// <summary>
/// Initializes a new instance of the <see cref="T:PageAttachmentDocument" /> class.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="name">The attachment name.</param>
/// <param name="provider">The file provider.</param>
/// <param name="dateTime">The modification date/time.</param>
public PageAttachmentDocument(PageInfo page, string name, string provider, DateTime dateTime) {
if(page == null) throw new ArgumentNullException("page");
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(provider == null) throw new ArgumentNullException("provider");
if(provider.Length == 0) throw new ArgumentException("Provider cannot be empty", "provider");
this.name = page.FullName + "|" + provider + "|" + name;
id = Tools.HashDocumentNameForTemporaryIndex(this.name);
title = name;
this.dateTime = dateTime;
this.page = page;
this.provider = provider;
}
/// <summary>
/// Initializes a new instance of the <see cref="T:PageAttachmentDocument" /> class.
/// </summary>
/// <param name="doc">The dumped document.</param>
public PageAttachmentDocument(DumpedDocument doc) {
string[] fields = doc.Name.Split('|');
id = doc.ID;
name = doc.Name;
title = doc.Title;
dateTime = doc.DateTime;
provider = fields[0];
page = Pages.FindPage(fields[1]);
}
/// <summary>
/// Gets or sets the globally unique ID of the document.
/// </summary>
public uint ID {
get { return id; }
set { id = value; }
}
/// <summary>
/// Gets the globally-unique name of the document.
/// </summary>
public string Name {
get { return name; }
}
/// <summary>
/// Gets the title of the document, if any.
/// </summary>
public string Title {
get { return title; }
}
/// <summary>
/// Gets the tag for the document type.
/// </summary>
public string TypeTag {
get { return typeTag; }
}
/// <summary>
/// Gets the document date/time.
/// </summary>
public DateTime DateTime {
get { return dateTime; }
}
/// <summary>
/// Performs the tokenization of the document content.
/// </summary>
/// <param name="content">The content to tokenize.</param>
/// <returns>The extracted words and their positions (always an empty array).</returns>
public WordInfo[] Tokenize(string content) {
return ScrewTurn.Wiki.SearchEngine.Tools.Tokenize(content);
}
/// <summary>
/// Gets the page.
/// </summary>
public PageInfo Page {
get { return page; }
}
/// <summary>
/// Gets the provider.
/// </summary>
public string Provider {
get { return provider; }
}
}
}

1523
Core/Pages.cs Normal file

File diff suppressed because it is too large Load diff

3073
Core/PagesStorageProvider.cs Normal file

File diff suppressed because it is too large Load diff

145
Core/Preferences.cs Normal file
View file

@ -0,0 +1,145 @@

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using ScrewTurn.Wiki.PluginFramework;
using System.Globalization;
namespace ScrewTurn.Wiki {
/// <summary>
/// Allows access to current user's preferences.
/// </summary>
public static class Preferences {
/// <summary>
/// Loads the language from a cookie.
/// </summary>
/// <returns>The language, or <c>null</c>.</returns>
public static string LoadLanguageFromCookie() {
HttpCookie cookie = HttpContext.Current.Request.Cookies[Settings.CultureCookieName];
if(cookie != null) {
string culture = cookie["C"];
return culture;
}
else return null;
}
/// <summary>
/// Loads the language from the current user's data.
/// </summary>
/// <returns>The language, or <c>null</c>.</returns>
public static string LoadLanguageFromUserData() {
UserInfo currentUser = SessionFacade.GetCurrentUser();
if(currentUser != null) {
string culture = Users.GetUserData(currentUser, "Culture");
return culture;
}
else return null;
}
/// <summary>
/// Loads the timezone from a cookie.
/// </summary>
/// <returns>The timezone, or <c>null</c>.</returns>
public static int? LoadTimezoneFromCookie() {
HttpCookie cookie = HttpContext.Current.Request.Cookies[Settings.CultureCookieName];
if(cookie != null) {
string timezone = cookie["T"];
return int.Parse(timezone, CultureInfo.InvariantCulture);
}
else return null;
}
/// <summary>
/// Loads the timezone from the current user's data.
/// </summary>
/// <returns>The timezone, or <c>null</c>.</returns>
public static int? LoadTimezoneFromUserData() {
UserInfo currentUser = SessionFacade.GetCurrentUser();
if(currentUser != null) {
string timezone = Users.GetUserData(currentUser, "Timezone");
if(timezone != null) return int.Parse(timezone, CultureInfo.InvariantCulture);
else return null;
}
else return null;
}
/// <summary>
/// Saves language and timezone preferences into a cookie.
/// </summary>
/// <param name="culture">The culture.</param>
/// <param name="timezone">The timezone.</param>
public static void SavePreferencesInCookie(string culture, int timezone) {
HttpCookie cookie = new HttpCookie(Settings.CultureCookieName);
cookie.Expires = DateTime.Now.AddYears(10);
cookie.Path = Settings.CookiePath;
cookie.Values.Add("C", culture);
cookie.Values.Add("T", timezone.ToString(CultureInfo.InvariantCulture));
HttpContext.Current.Response.Cookies.Add(cookie);
}
/// <summary>
/// Deletes the language and timezone preferences cookie.
/// </summary>
public static void DeletePreferencesCookie() {
HttpCookie cookie = new HttpCookie(Settings.CultureCookieName);
cookie.Expires = DateTime.Now.AddYears(-1);
cookie.Path = Settings.CookiePath;
cookie.Values.Add("C", null);
cookie.Values.Add("T", null);
HttpContext.Current.Request.Cookies.Add(cookie);
}
/// <summary>
/// Saves language and timezone preferences into the current user's data.
/// </summary>
/// <param name="culture">The culture.</param>
/// <param name="timezone">The timezone.</param>
/// <returns><c>true</c> if the data is stored, <c>false</c> otherwise.</returns>
public static bool SavePreferencesInUserData(string culture, int timezone) {
UserInfo user = SessionFacade.GetCurrentUser();
if(user != null && !user.Provider.UsersDataReadOnly) {
Users.SetUserData(user, "Culture", culture);
Users.SetUserData(user, "Timezone", timezone.ToString(CultureInfo.InvariantCulture));
return true;
}
else {
if(user == null) {
Log.LogEntry("Attempt to save user data when no user has logged in", EntryType.Warning, Log.SystemUsername);
}
return false;
}
}
/// <summary>
/// Aligns a date/time with the User's preferences (if any).
/// </summary>
/// <param name="dateTime">The date/time to align.</param>
/// <returns>The aligned date/time.</returns>
public static DateTime AlignWithTimezone(DateTime dateTime) {
// First, look for hard-stored user's preferences
// If they are not available, look at the cookie
int? tempShift = LoadTimezoneFromUserData();
if(!tempShift.HasValue) tempShift = LoadTimezoneFromCookie();
int shift = tempShift.HasValue ? tempShift.Value : Settings.DefaultTimezone;
return dateTime.ToUniversalTime().AddMinutes(shift + (dateTime.IsDaylightSavingTime() ? 60 : 0));
}
/// <summary>
/// Aligns a date/time with the default timezone.
/// </summary>
/// <param name="dateTime">The date/time to align.</param>
/// <returns>The aligned date/time.</returns>
public static DateTime AlignWithServerTimezone(DateTime dateTime) {
return dateTime.ToUniversalTime().AddMinutes(Settings.DefaultTimezone + (dateTime.IsDaylightSavingTime() ? 60 : 0));
}
}
}

View file

@ -0,0 +1,18 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ScrewTurn Wiki Core")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("b456637d-2218-43af-b6bf-c1b498c91630")]

71
Core/ProviderCollector.cs Normal file
View file

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Implements a generic Provider Collector.
/// </summary>
/// <typeparam name="T">The type of the Collector.</typeparam>
public class ProviderCollector<T> {
private List<T> list;
/// <summary>
/// Initializes a new instance of the class.
/// </summary>
public ProviderCollector() {
list = new List<T>(3);
}
/// <summary>
/// Adds a Provider to the Collector.
/// </summary>
/// <param name="provider">The Provider to add.</param>
public void AddProvider(T provider) {
lock(this) {
list.Add(provider);
}
}
/// <summary>
/// Removes a Provider from the Collector.
/// </summary>
/// <param name="provider">The Provider to remove.</param>
public void RemoveProvider(T provider) {
lock(this) {
list.Remove(provider);
}
}
/// <summary>
/// Gets all the Providers (copied array).
/// </summary>
public T[] AllProviders {
get {
lock(this) {
return list.ToArray();
}
}
}
/// <summary>
/// Gets a Provider, searching for its Type Name.
/// </summary>
/// <param name="typeName">The Type Name.</param>
/// <returns>The Provider, or null if the Provider was not found.</returns>
public T GetProvider(string typeName) {
lock(this) {
for(int i = 0; i < list.Count; i++) {
if(list[i].GetType().FullName.Equals(typeName)) return list[i];
}
return default(T);
}
}
}
}

556
Core/ProviderLoader.cs Normal file
View file

@ -0,0 +1,556 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;
using ScrewTurn.Wiki.PluginFramework;
using System.Globalization;
namespace ScrewTurn.Wiki {
/// <summary>
/// Loads providers from assemblies.
/// </summary>
public static class ProviderLoader {
// These must be const because they are used in switch constructs
internal const string UsersProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IUsersStorageProviderV30";
internal const string PagesProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IPagesStorageProviderV30";
internal const string FilesProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IFilesStorageProviderV30";
internal const string FormatterProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IFormatterProviderV30";
internal const string CacheProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.ICacheProviderV30";
internal static string SettingsStorageProviderAssemblyName = "";
/// <summary>
/// Verifies the read-only/read-write constraints of providers.
/// </summary>
/// <typeparam name="T">The type of the provider.</typeparam>
/// <param name="provider">The provider.</param>
/// <exception cref="T:ProviderConstraintException">Thrown when a constraint is not fulfilled.</exception>
private static void VerifyConstraints<T>(T provider) {
if(typeof(T) == typeof(IUsersStorageProviderV30)) {
// If the provider allows to write user accounts data, then group membership must be writeable too
IUsersStorageProviderV30 actualInstance = (IUsersStorageProviderV30)provider;
if(!actualInstance.UserAccountsReadOnly && actualInstance.GroupMembershipReadOnly) {
throw new ProviderConstraintException("If UserAccountsReadOnly is false, then also GroupMembershipReadOnly must be false");
}
}
}
/// <summary>
/// Tries to inizialize a provider.
/// </summary>
/// <typeparam name="T">The type of the provider, which must implement <b>IProvider</b>.</typeparam>
/// <param name="instance">The provider instance to initialize.</param>
/// <param name="collectorEnabled">The collector for enabled providers.</param>
/// <param name="collectorDisabled">The collector for disabled providers.</param>
private static void Initialize<T>(T instance, ProviderCollector<T> collectorEnabled,
ProviderCollector<T> collectorDisabled) where T : class, IProviderV30 {
if(collectorEnabled.GetProvider(instance.GetType().FullName) != null ||
collectorDisabled.GetProvider(instance.GetType().FullName) != null) {
Log.LogEntry("Provider " + instance.Information.Name + " already in memory", EntryType.Warning, Log.SystemUsername);
return;
}
bool enabled = !IsDisabled(instance.GetType().FullName);
try {
if(enabled) {
instance.Init(Host.Instance, LoadConfiguration(instance.GetType().FullName));
}
}
catch(InvalidConfigurationException) {
// Disable Provider
enabled = false;
Log.LogEntry("Unable to load provider " + instance.Information.Name + " (configuration rejected), disabling it", EntryType.Error, Log.SystemUsername);
SaveStatus(instance.GetType().FullName, false);
}
catch {
// Disable Provider
enabled = false;
Log.LogEntry("Unable to load provider " + instance.Information.Name + " (unknown error), disabling it", EntryType.Error, Log.SystemUsername);
SaveStatus(instance.GetType().FullName, false);
throw; // Exception is rethrown because it's not a normal condition
}
if(enabled) collectorEnabled.AddProvider(instance);
else collectorDisabled.AddProvider(instance);
// Verify constraints
VerifyConstraints<T>(instance);
Log.LogEntry("Provider " + instance.Information.Name + " loaded (" + (enabled ? "Enabled" : "Disabled") + ")", EntryType.General, Log.SystemUsername);
}
/// <summary>
/// Loads all the Providers and initialises them.
/// </summary>
/// <param name="loadUsers">A value indicating whether to load users storage providers.</param>
/// <param name="loadPages">A value indicating whether to load pages storage providers.</param>
/// <param name="loadFiles">A value indicating whether to load files storage providers.</param>
/// <param name="loadFormatters">A value indicating whether to load formatter providers.</param>
/// <param name="loadCache">A value indicating whether to load cache providers.</param>
public static void FullLoad(bool loadUsers, bool loadPages, bool loadFiles, bool loadFormatters, bool loadCache) {
string[] pluginAssemblies = Settings.Provider.ListPluginAssemblies();
List<IUsersStorageProviderV30> users = new List<IUsersStorageProviderV30>(2);
List<IUsersStorageProviderV30> dUsers = new List<IUsersStorageProviderV30>(2);
List<IPagesStorageProviderV30> pages = new List<IPagesStorageProviderV30>(2);
List<IPagesStorageProviderV30> dPages = new List<IPagesStorageProviderV30>(2);
List<IFilesStorageProviderV30> files = new List<IFilesStorageProviderV30>(2);
List<IFilesStorageProviderV30> dFiles = new List<IFilesStorageProviderV30>(2);
List<IFormatterProviderV30> forms = new List<IFormatterProviderV30>(2);
List<IFormatterProviderV30> dForms = new List<IFormatterProviderV30>(2);
List<ICacheProviderV30> cache = new List<ICacheProviderV30>(2);
List<ICacheProviderV30> dCache = new List<ICacheProviderV30>(2);
for(int i = 0; i < pluginAssemblies.Length; i++) {
IFilesStorageProviderV30[] d;
IUsersStorageProviderV30[] u;
IPagesStorageProviderV30[] p;
IFormatterProviderV30[] f;
ICacheProviderV30[] c;
LoadFrom(pluginAssemblies[i], out u, out p, out d, out f, out c);
if(loadFiles) files.AddRange(d);
if(loadUsers) users.AddRange(u);
if(loadPages) pages.AddRange(p);
if(loadFormatters) forms.AddRange(f);
if(loadCache) cache.AddRange(c);
}
// Init and add to the Collectors, starting from files providers
for(int i = 0; i < files.Count; i++) {
Initialize<IFilesStorageProviderV30>(files[i], Collectors.FilesProviderCollector, Collectors.DisabledFilesProviderCollector);
}
for(int i = 0; i < users.Count; i++) {
Initialize<IUsersStorageProviderV30>(users[i], Collectors.UsersProviderCollector, Collectors.DisabledUsersProviderCollector);
}
for(int i = 0; i < pages.Count; i++) {
Initialize<IPagesStorageProviderV30>(pages[i], Collectors.PagesProviderCollector, Collectors.DisabledPagesProviderCollector);
}
for(int i = 0; i < forms.Count; i++) {
Initialize<IFormatterProviderV30>(forms[i], Collectors.FormatterProviderCollector, Collectors.DisabledFormatterProviderCollector);
}
for(int i = 0; i < cache.Count; i++) {
Initialize<ICacheProviderV30>(cache[i], Collectors.CacheProviderCollector, Collectors.DisabledCacheProviderCollector);
}
}
/// <summary>
/// Loads the Configuration data of a Provider.
/// </summary>
/// <param name="typeName">The Type Name of the Provider.</param>
/// <returns>The Configuration, if available, otherwise an empty string.</returns>
public static string LoadConfiguration(string typeName) {
return Settings.Provider.GetPluginConfiguration(typeName);
}
/// <summary>
/// Saves the Configuration data of a Provider.
/// </summary>
/// <param name="typeName">The Type Name of the Provider.</param>
/// <param name="config">The Configuration data to save.</param>
public static void SaveConfiguration(string typeName, string config) {
Settings.Provider.SetPluginConfiguration(typeName, config);
}
/// <summary>
/// Saves the Status of a Provider.
/// </summary>
/// <param name="typeName">The Type Name of the Provider.</param>
/// <param name="enabled">A value specifying whether or not the Provider is enabled.</param>
public static void SaveStatus(string typeName, bool enabled) {
Settings.Provider.SetPluginStatus(typeName, enabled);
}
/// <summary>
/// Returns a value specifying whether or not a Provider is disabled.
/// </summary>
/// <param name="typeName">The Type Name of the Provider.</param>
/// <returns>True if the Provider is disabled.</returns>
public static bool IsDisabled(string typeName) {
return !Settings.Provider.GetPluginStatus(typeName);
}
/// <summary>
/// Loads Providers from an assembly.
/// </summary>
/// <param name="assembly">The path of the Assembly to load the Providers from.</param>
public static int LoadFromAuto(string assembly) {
IUsersStorageProviderV30[] users;
IPagesStorageProviderV30[] pages;
IFilesStorageProviderV30[] files;
IFormatterProviderV30[] forms;
ICacheProviderV30[] cache;
LoadFrom(assembly, out users, out pages, out files, out forms, out cache);
int count = 0;
// Init and add to the Collectors, starting from files providers
for(int i = 0; i < files.Length; i++) {
Initialize<IFilesStorageProviderV30>(files[i], Collectors.FilesProviderCollector, Collectors.DisabledFilesProviderCollector);
count++;
}
for(int i = 0; i < users.Length; i++) {
Initialize<IUsersStorageProviderV30>(users[i], Collectors.UsersProviderCollector, Collectors.DisabledUsersProviderCollector);
count++;
}
for(int i = 0; i < pages.Length; i++) {
Initialize<IPagesStorageProviderV30>(pages[i], Collectors.PagesProviderCollector, Collectors.DisabledPagesProviderCollector);
count++;
}
for(int i = 0; i < forms.Length; i++) {
Initialize<IFormatterProviderV30>(forms[i], Collectors.FormatterProviderCollector, Collectors.DisabledFormatterProviderCollector);
count++;
}
for(int i = 0; i < cache.Length; i++) {
Initialize<ICacheProviderV30>(cache[i], Collectors.CacheProviderCollector, Collectors.DisabledCacheProviderCollector);
count++;
}
return count;
}
/// <summary>
/// Loads Providers from an assembly.
/// </summary>
/// <param name="assembly">The path of the Assembly to load the Providers from.</param>
/// <param name="users">The Users Providers.</param>
/// <param name="files">The Files Providers.</param>
/// <param name="pages">The Pages Providers.</param>
/// <param name="formatters">The Formatter Providers.</param>
/// <param name="cache">The Cache Providers.</param>
/// <remarks>The Components returned are <b>not</b> initialized.</remarks>
public static void LoadFrom(string assembly, out IUsersStorageProviderV30[] users, out IPagesStorageProviderV30[] pages,
out IFilesStorageProviderV30[] files, out IFormatterProviderV30[] formatters, out ICacheProviderV30[] cache) {
Assembly asm = null;
try {
//asm = Assembly.LoadFile(assembly);
// This way the DLL is not locked and can be deleted at runtime
asm = Assembly.Load(LoadAssemblyFromProvider(Path.GetFileName(assembly)));
}
catch {
files = new IFilesStorageProviderV30[0];
users = new IUsersStorageProviderV30[0];
pages = new IPagesStorageProviderV30[0];
formatters = new IFormatterProviderV30[0];
cache = new ICacheProviderV30[0];
Log.LogEntry("Unable to load assembly " + Path.GetFileNameWithoutExtension(assembly), EntryType.Error, Log.SystemUsername);
return;
}
Type[] types = null;
try {
types = asm.GetTypes();
}
catch(ReflectionTypeLoadException) {
files = new IFilesStorageProviderV30[0];
users = new IUsersStorageProviderV30[0];
pages = new IPagesStorageProviderV30[0];
formatters = new IFormatterProviderV30[0];
cache = new ICacheProviderV30[0];
Log.LogEntry("Unable to load providers from (probably v2) assembly " + Path.GetFileNameWithoutExtension(assembly), EntryType.Error, Log.SystemUsername);
return;
}
List<IUsersStorageProviderV30> urs = new List<IUsersStorageProviderV30>();
List<IPagesStorageProviderV30> pgs = new List<IPagesStorageProviderV30>();
List<IFilesStorageProviderV30> fls = new List<IFilesStorageProviderV30>();
List<IFormatterProviderV30> frs = new List<IFormatterProviderV30>();
List<ICacheProviderV30> che = new List<ICacheProviderV30>();
Type[] interfaces;
for(int i = 0; i < types.Length; i++) {
// Avoid to load abstract classes as they cannot be instantiated
if(types[i].IsAbstract) continue;
interfaces = types[i].GetInterfaces();
foreach(Type iface in interfaces) {
if(iface == typeof(IUsersStorageProviderV30)) {
IUsersStorageProviderV30 tmpu = CreateInstance<IUsersStorageProviderV30>(asm, types[i]);
if(tmpu != null) {
urs.Add(tmpu);
Collectors.FileNames[tmpu.GetType().FullName] = assembly;
}
}
if(iface == typeof(IPagesStorageProviderV30)) {
IPagesStorageProviderV30 tmpp = CreateInstance<IPagesStorageProviderV30>(asm, types[i]);
if(tmpp != null) {
pgs.Add(tmpp);
Collectors.FileNames[tmpp.GetType().FullName] = assembly;
}
}
if(iface == typeof(IFilesStorageProviderV30)) {
IFilesStorageProviderV30 tmpd = CreateInstance<IFilesStorageProviderV30>(asm, types[i]);
if(tmpd != null) {
fls.Add(tmpd);
Collectors.FileNames[tmpd.GetType().FullName] = assembly;
}
}
if(iface == typeof(IFormatterProviderV30)) {
IFormatterProviderV30 tmpf = CreateInstance<IFormatterProviderV30>(asm, types[i]);
if(tmpf != null) {
frs.Add(tmpf);
Collectors.FileNames[tmpf.GetType().FullName] = assembly;
}
}
if(iface == typeof(ICacheProviderV30)) {
ICacheProviderV30 tmpc = CreateInstance<ICacheProviderV30>(asm, types[i]);
if(tmpc != null) {
che.Add(tmpc);
Collectors.FileNames[tmpc.GetType().FullName] = assembly;
}
}
}
}
users = urs.ToArray();
pages = pgs.ToArray();
files = fls.ToArray();
formatters = frs.ToArray();
cache = che.ToArray();
}
/// <summary>
/// Creates an instance of a type implementing a provider interface.
/// </summary>
/// <typeparam name="T">The provider interface type.</typeparam>
/// <param name="asm">The assembly that contains the type.</param>
/// <param name="type">The type to create an instance of.</param>
/// <returns>The instance, or <c>null</c>.</returns>
private static T CreateInstance<T>(Assembly asm, Type type) where T : class, IProviderV30 {
T instance;
try {
instance = asm.CreateInstance(type.ToString()) as T;
return instance;
}
catch {
Log.LogEntry("Unable to create instance of " + type.ToString(), EntryType.Error, Log.SystemUsername);
throw;
}
}
/// <summary>
/// Loads the content of an assembly from disk.
/// </summary>
/// <param name="assembly">The assembly file full path.</param>
/// <returns>The content of the assembly, in a byte array form.</returns>
private static byte[] LoadAssemblyFromDisk(string assembly) {
return File.ReadAllBytes(assembly);
}
/// <summary>
/// Loads the content of an assembly from the settings provider.
/// </summary>
/// <param name="assemblyName">The name of the assembly, such as "Assembly.dll".</param>
/// <returns>The content fo the assembly.</returns>
private static byte[] LoadAssemblyFromProvider(string assemblyName) {
return Settings.Provider.RetrievePluginAssembly(assemblyName);
}
/// <summary>
/// Loads the proper Setting Storage Provider, given its name.
/// </summary>
/// <param name="name">The fully qualified name (such as "Namespace.ProviderClass, MyAssembly"), or <c>null</c>/<b>String.Empty</b>/"<b>default</b>" for the default provider.</param>
/// <returns>The settings storage provider.</returns>
public static ISettingsStorageProviderV30 LoadSettingsStorageProvider(string name) {
if(name == null || name.Length == 0 || string.Compare(name, "default", true, CultureInfo.InvariantCulture) == 0) {
return new SettingsStorageProvider();
}
ISettingsStorageProviderV30 result = null;
Exception inner = null;
if(name.Contains(",")) {
string[] fields = name.Split(',');
if(fields.Length == 2) {
fields[0] = fields[0].Trim(' ', '"');
fields[1] = fields[1].Trim(' ', '"');
try {
// assemblyName should be an absolute path or a relative path in bin or public\Plugins
Assembly asm;
Type t;
string assemblyName = fields[1];
if(!assemblyName.ToLowerInvariant().EndsWith(".dll")) assemblyName += ".dll";
if(File.Exists(assemblyName)) {
asm = Assembly.Load(LoadAssemblyFromDisk(assemblyName));
t = asm.GetType(fields[0]);
SettingsStorageProviderAssemblyName = Path.GetFileName(assemblyName);
}
else {
string tentativePluginsPath = null;
try {
// Settings.PublicDirectory is only available when running the web app
tentativePluginsPath = Path.Combine(Settings.PublicDirectory, "Plugins");
tentativePluginsPath = Path.Combine(tentativePluginsPath, assemblyName);
}
catch { }
if(!string.IsNullOrEmpty(tentativePluginsPath) && File.Exists(tentativePluginsPath)) {
asm = Assembly.Load(LoadAssemblyFromDisk(tentativePluginsPath));
t = asm.GetType(fields[0]);
SettingsStorageProviderAssemblyName = Path.GetFileName(tentativePluginsPath);
}
else {
// Trim .dll
t = Type.GetType(fields[0] + "," + assemblyName.Substring(0, assemblyName.Length - 4), true, true);
SettingsStorageProviderAssemblyName = assemblyName;
}
}
result = t.GetConstructor(new Type[0]).Invoke(new object[0]) as ISettingsStorageProviderV30;
}
catch(Exception ex) {
inner = ex;
result = null;
}
}
}
if(result == null) throw new ArgumentException("Could not load the specified Settings Storage Provider", inner);
else return result;
}
/// <summary>
/// Loads all settings storage providers available in all DLLs stored in a provider.
/// </summary>
/// <param name="repository">The input provider.</param>
/// <returns>The providers found (not initialized).</returns>
public static ISettingsStorageProviderV30[] LoadAllSettingsStorageProviders(ISettingsStorageProviderV30 repository) {
// This method is actually a memory leak because it can be executed multimple times
// Every time it loads a set of assemblies which cannot be unloaded (unless a separate AppDomain is used)
List<ISettingsStorageProviderV30> result = new List<ISettingsStorageProviderV30>();
foreach(string dll in repository.ListPluginAssemblies()) {
byte[] asmBin = repository.RetrievePluginAssembly(dll);
Assembly asm = Assembly.Load(asmBin);
Type[] types = null;
try {
types = asm.GetTypes();
}
catch(ReflectionTypeLoadException) {
// Skip assembly
Log.LogEntry("Unable to load providers from (probably v2) assembly " + Path.GetFileNameWithoutExtension(dll), EntryType.Error, Log.SystemUsername);
continue;
}
foreach(Type type in types) {
// Avoid to load abstract classes as they cannot be instantiated
if(type.IsAbstract) continue;
Type[] interfaces = type.GetInterfaces();
foreach(Type iface in interfaces) {
if(iface == typeof(ISettingsStorageProviderV30)) {
try {
ISettingsStorageProviderV30 temp = asm.CreateInstance(type.ToString()) as ISettingsStorageProviderV30;
if(temp != null) result.Add(temp);
}
catch { }
}
}
}
}
return result.ToArray();
}
/// <summary>
/// Tries to change a provider's configuration.
/// </summary>
/// <param name="typeName">The provider.</param>
/// <param name="configuration">The new configuration.</param>
/// <param name="error">The error message, if any.</param>
/// <returns><c>true</c> if the configuration is saved, <c>false</c> if the provider rejected it.</returns>
public static bool TryChangeConfiguration(string typeName, string configuration, out string error) {
error = null;
bool enabled, canDisable;
IProviderV30 provider = Collectors.FindProvider(typeName, out enabled, out canDisable);
try {
provider.Init(Host.Instance, configuration);
}
catch(InvalidConfigurationException icex) {
error = icex.Message;
return false;
}
SaveConfiguration(typeName, configuration);
return true;
}
/// <summary>
/// Disables a provider.
/// </summary>
/// <param name="typeName">The provider to disable.</param>
public static void DisableProvider(string typeName) {
bool enabled, canDisable;
IProviderV30 provider = Collectors.FindProvider(typeName, out enabled, out canDisable);
if(enabled && canDisable) {
provider.Shutdown();
Collectors.TryDisable(typeName);
SaveStatus(typeName, false);
}
}
/// <summary>
/// Enables a provider.
/// </summary>
/// <param name="typeName">The provider to enable.</param>
public static void EnableProvider(string typeName) {
bool enabled, canDisable;
IProviderV30 provider = Collectors.FindProvider(typeName, out enabled, out canDisable);
if(!enabled) {
provider.Init(Host.Instance, LoadConfiguration(typeName));
Collectors.TryEnable(typeName);
SaveStatus(typeName, true);
}
}
/// <summary>
/// Unloads a provider from memory.
/// </summary>
/// <param name="typeName">The provider to unload.</param>
public static void UnloadProvider(string typeName) {
DisableProvider(typeName);
Collectors.TryUnload(typeName);
}
}
/// <summary>
/// Defines an exception thrown when a constraint is not fulfilled by a provider.
/// </summary>
public class ProviderConstraintException : Exception {
/// <summary>
/// Initializes a new instance of the <see cref="T:ProviderConstraintException" /> class.
/// </summary>
/// <param name="message">The message.</param>
public ProviderConstraintException(string message)
: base(message) { }
}
}

131
Core/ProviderUpdater.cs Normal file
View file

@ -0,0 +1,131 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using System.Net;
using System.IO;
namespace ScrewTurn.Wiki {
/// <summary>
/// Handles the updating of providers.
/// </summary>
public class ProviderUpdater {
private List<string> visitedUrls;
private ISettingsStorageProviderV30 settingsProvider;
private List<IProviderV30> providers;
private Dictionary<string, string> fileNamesForProviders;
/// <summary>
/// Initializes a new instance of the <see cref="T:ProviderUpdater" /> class.
/// </summary>
/// <param name="settingsProvider">The settings storage provider.</param>
/// <param name="fileNamesForProviders">A provider->file dictionary.</param>
/// <param name="providers">The providers to update.</param>
public ProviderUpdater(ISettingsStorageProviderV30 settingsProvider,
Dictionary<string, string> fileNamesForProviders,
params IProviderV30[][] providers) {
if(settingsProvider == null) throw new ArgumentNullException("settingsProvider");
if(fileNamesForProviders == null) throw new ArgumentNullException("fileNamesForProviders");
if(providers == null) throw new ArgumentNullException("providers");
if(providers.Length == 0) throw new ArgumentException("Providers cannot be empty", "providers");
this.settingsProvider = settingsProvider;
this.fileNamesForProviders = fileNamesForProviders;
this.providers = new List<IProviderV30>(20);
foreach(IProviderV30[] group in providers) {
this.providers.AddRange(group);
}
visitedUrls = new List<string>(10);
}
/// <summary>
/// Updates all the providers.
/// </summary>
/// <returns>The number of updated DLLs.</returns>
public int UpdateAll() {
Log.LogEntry("Starting automatic providers update", EntryType.General, Log.SystemUsername);
int updatedDlls = 0;
foreach(IProviderV30 prov in providers) {
if(string.IsNullOrEmpty(prov.Information.UpdateUrl)) continue;
string newVersion;
string newDllUrl;
UpdateStatus status = Tools.GetUpdateStatus(prov.Information.UpdateUrl,
prov.Information.Version, out newVersion, out newDllUrl);
if(status == UpdateStatus.NewVersionFound && !string.IsNullOrEmpty(newDllUrl)) {
// Update is possible
// Case insensitive check
if(!visitedUrls.Contains(newDllUrl.ToLowerInvariant())) {
string dllName = null;
if(!fileNamesForProviders.TryGetValue(prov.GetType().FullName, out dllName)) {
Log.LogEntry("Could not determine DLL name for provider " + prov.GetType().FullName, EntryType.Error, Log.SystemUsername);
continue;
}
// Download DLL and install
if(DownloadAndUpdateDll(prov, newDllUrl, dllName)) {
visitedUrls.Add(newDllUrl.ToLowerInvariant());
updatedDlls++;
}
}
else {
// Skip DLL (already updated)
Log.LogEntry("Skipping provider " + prov.GetType().FullName + ": DLL already updated", EntryType.General, Log.SystemUsername);
}
}
}
Log.LogEntry("Automatic providers update completed: updated " + updatedDlls.ToString() + " DLLs", EntryType.General, Log.SystemUsername);
return updatedDlls;
}
/// <summary>
/// Downloads and updates a DLL.
/// </summary>
/// <param name="provider">The provider.</param>
/// <param name="url">The URL of the new DLL.</param>
/// <param name="filename">The file name of the DLL.</param>
private bool DownloadAndUpdateDll(IProviderV30 provider, string url, string filename) {
try {
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if(response.StatusCode != HttpStatusCode.OK) {
Log.LogEntry("Update failed for provider " + provider.GetType().FullName + ": Status Code=" + response.StatusCode.ToString(), EntryType.Error, Log.SystemUsername);
response.Close();
return false;
}
BinaryReader reader = new BinaryReader(response.GetResponseStream());
byte[] content = reader.ReadBytes((int)response.ContentLength);
reader.Close();
bool done = settingsProvider.StorePluginAssembly(filename, content);
if(done) Log.LogEntry("Provider " + provider.GetType().FullName + " updated", EntryType.General, Log.SystemUsername);
else Log.LogEntry("Update failed for provider " + provider.GetType().FullName + ": could not store assembly", EntryType.Error, Log.SystemUsername);
return done;
}
catch(Exception ex) {
Log.LogEntry("Update failed for provider " + provider.GetType().FullName + ": " + ex.ToString(), EntryType.Error, Log.SystemUsername);
return false;
}
}
}
}

44
Core/RecentChanges.cs Normal file
View file

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Manages the Wiki's Recent Changes.
/// </summary>
public static class RecentChanges {
/// <summary>
/// Gets all the changes, sorted by date/time ascending.
/// </summary>
public static RecentChange[] GetAllChanges() {
RecentChange[] changes = Settings.Provider.GetRecentChanges();
RecentChange[] myCopy = new RecentChange[changes.Length];
Array.Copy(changes, myCopy, changes.Length);
Array.Sort(myCopy, (x, y) => { return x.DateTime.CompareTo(y.DateTime); });
return myCopy;
}
/// <summary>
/// Adds a new change.
/// </summary>
/// <param name="page">The page name.</param>
/// <param name="title">The page title.</param>
/// <param name="messageSubject">The message subject.</param>
/// <param name="dateTime">The date/time.</param>
/// <param name="user">The user.</param>
/// <param name="change">The change.</param>
/// <param name="descr">The description (optional).</param>
public static void AddChange(string page, string title, string messageSubject, DateTime dateTime, string user, Change change, string descr) {
Settings.Provider.AddRecentChange(page, title, messageSubject, dateTime, user, change, descr);
}
}
}

61
Core/Redirections.cs Normal file
View file

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Manages information about Page Redirections.
/// </summary>
public static class Redirections {
/// <summary>
/// Adds a new Redirection.
/// </summary>
/// <param name="source">The source Page.</param>
/// <param name="destination">The destination Page.</param>
/// <returns>True if the Redirection is added, false otherwise.</returns>
/// <remarks>The method prevents circular and multi-level redirection.</remarks>
public static void AddRedirection(PageInfo source, PageInfo destination) {
if(source == null) throw new ArgumentNullException("source");
if(destination == null) throw new ArgumentNullException("destination");
Cache.Provider.AddRedirection(source.FullName, destination.FullName);
}
/// <summary>
/// Gets the destination Page.
/// </summary>
/// <param name="page">The source Page.</param>
/// <returns>The destination Page, or null.</returns>
public static PageInfo GetDestination(PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
string destination = Cache.Provider.GetRedirectionDestination(page.FullName);
if(string.IsNullOrEmpty(destination)) return null;
else return Pages.FindPage(destination);
}
/// <summary>
/// Removes any occurrence of a Page from the redirection table, both on sources and destinations.
/// </summary>
/// <param name="page">The Page to wipe-out.</param>
/// <remarks>This method is useful when removing a Page.</remarks>
public static void WipePageOut(PageInfo page) {
if(page == null) throw new ArgumentNullException("page");
Cache.Provider.RemovePageFromRedirections(page.FullName);
}
/// <summary>
/// Clears the Redirection table.
/// </summary>
public static void Clear() {
Cache.Provider.ClearRedirections();
}
}
}

1127
Core/ReverseFormatter.cs Normal file

File diff suppressed because it is too large Load diff

210
Core/SearchTools.cs Normal file
View file

@ -0,0 +1,210 @@

using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using ScrewTurn.Wiki.SearchEngine;
namespace ScrewTurn.Wiki {
/// <summary>
/// Implements tools for searching through the wiki.
/// </summary>
public static class SearchTools {
/// <summary>
/// Searches for pages with name or title similar to a specified value.
/// </summary>
/// <param name="name">The name to look for (<c>null</c> for the root).</param>
/// <param name="nspace">The namespace to search into.</param>
/// <returns>The similar pages, if any.</returns>
public static PageInfo[] SearchSimilarPages(string name, string nspace) {
if(string.IsNullOrEmpty(nspace)) nspace = null;
SearchResultCollection searchResults = Search(name, false, false, SearchOptions.AtLeastOneWord);
List<PageInfo> result = new List<PageInfo>(20);
foreach(SearchResult res in searchResults) {
PageDocument pageDoc = res.Document as PageDocument;
if(pageDoc != null) {
string pageNamespace = NameTools.GetNamespace(pageDoc.PageInfo.FullName);
if(string.IsNullOrEmpty(pageNamespace)) pageNamespace = null;
if(pageNamespace == nspace) {
result.Add(pageDoc.PageInfo);
}
}
}
// Search page names for matches
List<PageInfo> allPages = Pages.GetPages(Pages.FindNamespace(nspace));
PageNameComparer comp = new PageNameComparer();
string currentName = name.ToLowerInvariant();
foreach(PageInfo page in allPages) {
if(NameTools.GetLocalName(page.FullName).ToLowerInvariant().Contains(currentName)) {
if(result.Find(delegate(PageInfo p) { return comp.Compare(p, page) == 0; }) == null) {
result.Add(page);
}
}
}
return result.ToArray();
}
/// <summary>
/// Performs a search in the wiki.
/// </summary>
/// <param name="query">The search query.</param>
/// <param name="fullText">A value indicating whether to perform a full-text search.</param>
/// <param name="searchFilesAndAttachments">A value indicating whether to search through files and attachments.</param>
/// <param name="options">The search options.</param>
/// <returns>The results collection.</returns>
public static SearchResultCollection Search(string query, bool fullText, bool searchFilesAndAttachments, SearchOptions options) {
// First, search regular page content...
List<SearchResultCollection> allCollections = new List<SearchResultCollection>(3);
foreach(IPagesStorageProviderV30 prov in Collectors.PagesProviderCollector.AllProviders) {
SearchResultCollection currentResults = prov.PerformSearch(new SearchParameters(query, options));
if(!fullText) {
// All non title-related matches must be removed
SearchResultCollection filteredResults = new SearchResultCollection(10);
foreach(SearchResult res in currentResults) {
foreach(WordInfo word in res.Matches) {
if(word.Location == WordLocation.Title) {
filteredResults.Add(res);
break;
}
}
}
allCollections.Add(filteredResults);
}
else allCollections.Add(currentResults);
}
// ... normalize relevance based on the number of providers
float providerNormalizationFactor = 1F / (float)Collectors.PagesProviderCollector.AllProviders.Length;
foreach(SearchResultCollection coll in allCollections) {
foreach(SearchResult result in coll) {
result.Relevance.NormalizeAfterFinalization(providerNormalizationFactor);
}
}
if(searchFilesAndAttachments) {
// ... then build a temporary index for files and attachments...
StandardIndex temporaryIndex = new StandardIndex();
uint tempDocumentId = 1;
uint tempWordId = 1;
temporaryIndex.IndexChanged += delegate(object sender, IndexChangedEventArgs e) {
if(e.Change == IndexChangeType.DocumentAdded) {
List<WordId> ids = null;
if(e.ChangeData.Words != null) {
ids = new List<WordId>(20);
foreach(DumpedWord d in e.ChangeData.Words) {
ids.Add(new WordId(d.Text, tempWordId));
tempWordId++;
}
}
e.Result = new IndexStorerResult(tempDocumentId, ids);
tempDocumentId++;
}
};
temporaryIndex.SetBuildDocumentDelegate(DetectFileOrAttachment);
foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) {
TraverseDirectories(temporaryIndex, prov, null);
string[] pagesWithAttachments = prov.GetPagesWithAttachments();
foreach(string page in pagesWithAttachments) {
// Store attachments for the current page in the index
PageInfo pageInfo = Pages.FindPage(page);
// pageInfo can be null if the index is corrupted
if(pageInfo != null) {
foreach(string attachment in prov.ListPageAttachments(pageInfo)) {
FileDetails details = prov.GetPageAttachmentDetails(pageInfo, attachment);
temporaryIndex.StoreDocument(new PageAttachmentDocument(pageInfo,
attachment, prov.GetType().FullName, details.LastModified),
new string[0], "", null);
}
}
}
}
// ... then search in the temporary index and normalize relevance
SearchResultCollection filesAndAttachments = temporaryIndex.Search(new SearchParameters(query, options));
providerNormalizationFactor = 1F / (float)Collectors.FilesProviderCollector.AllProviders.Length;
foreach(SearchResult result in filesAndAttachments) {
result.Relevance.NormalizeAfterFinalization(providerNormalizationFactor);
}
allCollections.Add(filesAndAttachments);
}
return CombineCollections(allCollections);
}
/// <summary>
/// Detects the document in a dumped instance for files and attachments.
/// </summary>
/// <param name="doc">The dumped document instance.</param>
/// <returns>The proper document instance.</returns>
private static IDocument DetectFileOrAttachment(DumpedDocument doc) {
if(doc.TypeTag == FileDocument.StandardTypeTag) {
return new FileDocument(doc);
}
else if(doc.TypeTag == PageAttachmentDocument.StandardTypeTag) {
return new PageAttachmentDocument(doc);
}
else throw new NotSupportedException();
}
/// <summary>
/// Traverses a directory tree, indexing all files.
/// </summary>
/// <param name="index">The output index.</param>
/// <param name="provider">The provider.</param>
/// <param name="currentDir">The current directory.</param>
private static void TraverseDirectories(InMemoryIndexBase index, IFilesStorageProviderV30 provider, string currentDir) {
// Store files in the index
foreach(string file in provider.ListFiles(currentDir)) {
FileDetails details = provider.GetFileDetails(file);
index.StoreDocument(new FileDocument(file, provider.GetType().FullName, details.LastModified),
new string[0], "", null);
}
// Recursively process all sub-directories
foreach(string directory in provider.ListDirectories(currentDir)) {
TraverseDirectories(index, provider, directory);
}
}
/// <summary>
/// Combines a set of <see cref="T:SearchResultCollection" />s into a single object.
/// </summary>
/// <param name="collections">The collections.</param>
/// <returns>The resulting <see cref="T:SearchResultCollection" />.</returns>
private static SearchResultCollection CombineCollections(List<SearchResultCollection> collections) {
List<SearchResult> tempResults = new List<SearchResult>(100);
foreach(SearchResultCollection coll in collections) {
tempResults.AddRange(coll);
}
tempResults.Sort(delegate(SearchResult x, SearchResult y) { return y.Relevance.Value.CompareTo(x.Relevance.Value); });
SearchResultCollection resultCollection = new SearchResultCollection(50);
foreach(SearchResult singleResult in tempResults) {
resultCollection.Add(singleResult);
}
return resultCollection;
}
}
}

194
Core/SessionFacade.cs Normal file
View file

@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.SessionState;
using System.Web;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Exposes in a strongly-typed fashion the Session variables.
/// </summary>
public static class SessionFacade {
/// <summary>
/// Gets the current Session object.
/// </summary>
private static HttpSessionState Session {
get { return HttpContext.Current.Session; }
}
/// <summary>
/// Gets or sets the Login Key.
/// </summary>
public static string LoginKey {
get { return Session != null ? (string)Session["LoginKey"] : null; }
set { if(Session != null) Session["LoginKey"] = value; }
}
/// <summary>
/// Gets or sets the current username, or <c>null</c>.
/// </summary>
public static string CurrentUsername {
get { return Session != null ? Session["Username"] as string : null; }
set { if(Session != null) Session["Username"] = value; }
}
/// <summary>
/// Gets the current user, if any.
/// </summary>
/// <returns>The current user, or <c>null</c>.</returns>
public static UserInfo GetCurrentUser() {
if(Session != null) {
string sessionId = Session.SessionID;
UserInfo current = SessionCache.GetCurrentUser(sessionId);
if(current != null) return current;
else {
string un = CurrentUsername;
if(string.IsNullOrEmpty(un)) return null;
else if(object.ReferenceEquals(un, AnonymousUsername) || un == AnonymousUsername) return Users.GetAdministratorAccount();
else {
current = Users.FindUser(un);
SessionCache.SetCurrentUser(sessionId, current);
return current;
}
}
}
else return null;
}
/// <summary>
/// The username for anonymous users.
/// </summary>
public const string AnonymousUsername = "|";
/// <summary>
/// Gets the current username for security checks purposes only.
/// </summary>
/// <returns>The current username, or <b>AnonymousUsername</b> if anonymous.</returns>
public static string GetCurrentUsername() {
string un = CurrentUsername;
if(string.IsNullOrEmpty(un)) return AnonymousUsername;
else return un;
}
/// <summary>
/// Gets the current user groups.
/// </summary>
public static UserGroup[] GetCurrentGroups() {
if(Session != null) {
string sessionId = Session.SessionID;
UserGroup[] groups = SessionCache.GetCurrentGroups(sessionId);
if(groups == null || groups.Length == 0) {
UserInfo current = GetCurrentUser();
if(current != null) {
groups = new UserGroup[current.Groups.Length];
for(int i = 0; i < groups.Length; i++) {
groups[i] = Users.FindUserGroup(current.Groups[i]);
}
}
else {
groups = new UserGroup[] { Users.FindUserGroup(Settings.AnonymousGroup) };
}
SessionCache.SetCurrentGroups(sessionId, groups);
}
return groups;
}
else return new UserGroup[0];
}
/// <summary>
/// Gets the current group names.
/// </summary>
/// <returns>The group names.</returns>
public static string[] GetCurrentGroupNames() {
return Array.ConvertAll(GetCurrentGroups(), delegate(UserGroup g) { return g.Name; });
}
/// <summary>
/// Gets the Breadcrumbs Manager.
/// </summary>
public static BreadcrumbsManager Breadcrumbs {
get { return new BreadcrumbsManager(); }
}
}
/// <summary>
/// Implements a session data cache whose lifetime is only limited to one request.
/// </summary>
public static class SessionCache {
private static Dictionary<string, UserInfo> currentUsers = new Dictionary<string, UserInfo>(100);
private static Dictionary<string, UserGroup[]> currentGroups = new Dictionary<string, UserGroup[]>(100);
/// <summary>
/// Gets the current user, if any, of a session.
/// </summary>
/// <param name="sessionId">The session ID.</param>
/// <returns>The current user, or <c>null</c>.</returns>
public static UserInfo GetCurrentUser(string sessionId) {
lock(currentUsers) {
UserInfo result = null;
currentUsers.TryGetValue(sessionId, out result);
return result;
}
}
/// <summary>
/// Sets the current user of a session.
/// </summary>
/// <param name="sessionId">The session ID.</param>
/// <param name="user">The user.</param>
public static void SetCurrentUser(string sessionId, UserInfo user) {
lock(currentUsers) {
currentUsers[sessionId] = user;
}
}
/// <summary>
/// Gets the current groups, if any, of a session.
/// </summary>
/// <param name="sessionId">The session ID.</param>
/// <returns>The groups, or <b>null.</b></returns>
public static UserGroup[] GetCurrentGroups(string sessionId) {
lock(currentGroups) {
UserGroup[] result = null;
currentGroups.TryGetValue(sessionId, out result);
return result;
}
}
/// <summary>
/// Sets the current groups of a session.
/// </summary>
/// <param name="sessionId">The session ID.</param>
/// <param name="groups">The groups.</param>
public static void SetCurrentGroups(string sessionId, UserGroup[] groups) {
lock(currentGroups) {
currentGroups[sessionId] = groups;
}
}
/// <summary>
/// Clears all cached data of a session.
/// </summary>
/// <param name="sessionId">The session ID.</param>
public static void ClearData(string sessionId) {
lock(currentUsers) {
currentUsers.Remove(sessionId);
}
lock(currentGroups) {
currentGroups.Remove(sessionId);
}
}
}
}

1233
Core/Settings.cs Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

137
Core/Snippets.cs Normal file
View file

@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.IO;
using ScrewTurn.Wiki.PluginFramework;
using System.Text.RegularExpressions;
namespace ScrewTurn.Wiki {
/// <summary>
/// Manages snippets.
/// </summary>
public static class Snippets {
/// <summary>
/// Gets the complete list of the Snippets.
/// </summary>
/// <returns>The snippets, sorted by name.</returns>
public static List<Snippet> GetSnippets() {
List<Snippet> allSnippets = new List<Snippet>(50);
// Retrieve all snippets from Pages Provider
foreach(IPagesStorageProviderV30 provider in Collectors.PagesProviderCollector.AllProviders) {
allSnippets.AddRange(provider.GetSnippets());
}
allSnippets.Sort(new SnippetNameComparer());
return allSnippets;
}
/// <summary>
/// Finds a Snippet.
/// </summary>
/// <param name="name">The Name of the Snippet to find.</param>
/// <returns>The Snippet or null if it is not found.</returns>
public static Snippet Find(string name) {
List<Snippet> allSnippets = GetSnippets();
int result = allSnippets.BinarySearch(new Snippet(name, "", null), new SnippetNameComparer());
if(allSnippets.Count > 0 && result >= 0) return allSnippets[result];
else return null;
}
/// <summary>
/// Creates a new Snippet.
/// </summary>
/// <param name="name">The name of the Snippet.</param>
/// <param name="content">The content of the Snippet.</param>
/// <param name="provider">The Provider to use to store the Snippet (<c>null</c> for the default provider).</param>
/// <returns>True if the Snippets has been addedd successfully.</returns>
public static bool AddSnippet(string name, string content, IPagesStorageProviderV30 provider) {
if(Find(name) != null) return false;
if(provider == null) provider = Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider);
Snippet newSnippet = provider.AddSnippet(name, content);
if(newSnippet != null) {
Log.LogEntry("Snippet " + name + " created", EntryType.General, Log.SystemUsername);
Content.ClearPseudoCache();
Content.InvalidateAllPages();
}
else Log.LogEntry("Creation failed for Snippet " + name, EntryType.Error, Log.SystemUsername);
return newSnippet != null;
}
/// <summary>
/// Removes a Snippet.
/// </summary>
/// <param name="snippet">The Snippet to remove.</param>
/// <returns>True if the Snippet has been removed successfully.</returns>
public static bool RemoveSnippet(Snippet snippet) {
bool done = snippet.Provider.RemoveSnippet(snippet.Name);
if(done) {
Log.LogEntry("Snippet " + snippet.Name + " deleted", EntryType.General, Log.SystemUsername);
Content.ClearPseudoCache();
Content.InvalidateAllPages();
}
else Log.LogEntry("Deletion failed for Snippet " + snippet.Name, EntryType.Error, Log.SystemUsername);
return done;
}
/// <summary>
/// Modifies the Content of a Snippet.
/// </summary>
/// <param name="snippet">The Snippet to update.</param>
/// <param name="content">The new Content.</param>
/// <returns>True if the Snippet has been updated successfully.</returns>
public static bool ModifySnippet(Snippet snippet, string content) {
Snippet newSnippet = snippet.Provider.ModifySnippet(snippet.Name, content);
if(newSnippet != null) {
Log.LogEntry("Snippet " + snippet.Name + " updated", EntryType.General, Log.SystemUsername);
Content.ClearPseudoCache();
Content.InvalidateAllPages();
}
else Log.LogEntry("Modification failed for Snippet " + snippet.Name, EntryType.Error, Log.SystemUsername);
return newSnippet != null;
}
/// <summary>
/// The regular expression to use for extracting parameters.
/// </summary>
public static readonly Regex ParametersRegex = new Regex("\\?[a-zA-Z0-9_-]+\\?", RegexOptions.Compiled | RegexOptions.CultureInvariant);
/// <summary>
/// Counts the parameters in a snippet.
/// </summary>
/// <param name="snippet">The snippet.</param>
/// <returns>The number of parameters.</returns>
public static int CountParameters(Snippet snippet) {
return ExtractParameterNames(snippet).Length;
}
/// <summary>
/// Finds the parameters in a snippet.
/// </summary>
/// <param name="snippet">The snippet.</param>
/// <returns>The parameter names.</returns>
public static string[] ExtractParameterNames(Snippet snippet) {
List<string> parms = new List<string>();
foreach(Match m in ParametersRegex.Matches(snippet.Content)) {
string value = m.Value.Substring(1, m.Value.Length - 2);
if(m.Success && !parms.Contains(value)) parms.Add(value);
}
return parms.ToArray();
}
}
}

436
Core/StartupTools.cs Normal file
View file

@ -0,0 +1,436 @@
using System;
using System.IO;
using System.Resources;
using System.Web.Configuration;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Provides tools for starting and shutting down the wiki engine.
/// </summary>
public static class StartupTools {
/// <summary>
/// Gets the Settings Storage Provider configuration string from web.config.
/// </summary>
/// <returns>The configuration string.</returns>
public static string GetSettingsStorageProviderConfiguration() {
string config = WebConfigurationManager.AppSettings["SettingsStorageProviderConfig"];
if(config != null) return config;
else return "";
}
/// <summary>
/// Updates the DLLs into the settings storage provider, if appropriate.
/// </summary>
/// <param name="provider">The provider.</param>
/// <param name="settingsProviderAsmName">The file name of the assembly that contains the current Settings Storage Provider.</param>
private static void UpdateDllsIntoSettingsProvider(ISettingsStorageProviderV30 provider, string settingsProviderAsmName) {
// Look into public\Plugins (hardcoded)
string fullPath = Path.Combine(Settings.PublicDirectory, "Plugins");
if(!Directory.Exists(fullPath)) return;
string[] dlls = Directory.GetFiles(fullPath, "*.dll");
string[] installedDlls = provider.ListPluginAssemblies();
foreach(string dll in dlls) {
bool found = false;
string filename = Path.GetFileName(dll);
foreach(string instDll in installedDlls) {
if(instDll.ToLowerInvariant() == filename.ToLowerInvariant()) {
found = true;
break;
}
}
if(!found && filename.ToLowerInvariant() == settingsProviderAsmName.ToLowerInvariant()) {
found = true;
}
if(found) {
// Update DLL
provider.StorePluginAssembly(filename, File.ReadAllBytes(dll));
}
}
}
/// <summary>
/// Performs all needed startup operations.
/// </summary>
public static void Startup() {
// Load Host
Host.Instance = new Host();
// Load config
ISettingsStorageProviderV30 ssp = ProviderLoader.LoadSettingsStorageProvider(WebConfigurationManager.AppSettings["SettingsStorageProvider"]);
ssp.Init(Host.Instance, GetSettingsStorageProviderConfiguration());
Collectors.SettingsProvider = ssp;
//Settings.Instance = new Settings(ssp);
if(!(ssp is SettingsStorageProvider)) {
// Update DLLs from public\Plugins
UpdateDllsIntoSettingsProvider(ssp, ProviderLoader.SettingsStorageProviderAssemblyName);
}
// Initialize authorization managers
//AuthReader.Instance = new AuthReader(Settings.Provider);
//AuthWriter.Instance = new AuthWriter(Settings.Provider);
//AuthChecker.Instance = new AuthChecker(Settings.Provider);
if(ssp.GetMetaDataItem(MetaDataItem.AccountActivationMessage, null) == "")
ssp.SetMetaDataItem(MetaDataItem.AccountActivationMessage, null, Defaults.AccountActivationMessageContent);
if(ssp.GetMetaDataItem(MetaDataItem.EditNotice, null) == "")
ssp.SetMetaDataItem(MetaDataItem.EditNotice, null, Defaults.EditNoticeContent);
if(ssp.GetMetaDataItem(MetaDataItem.Footer, null) == "")
ssp.SetMetaDataItem(MetaDataItem.Footer, null, Defaults.FooterContent);
if(ssp.GetMetaDataItem(MetaDataItem.Header, null) == "")
ssp.SetMetaDataItem(MetaDataItem.Header, null, Defaults.HeaderContent);
if(ssp.GetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null) == "")
ssp.SetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null, Defaults.PasswordResetProcedureMessageContent);
if(ssp.GetMetaDataItem(MetaDataItem.Sidebar, null) == "")
ssp.SetMetaDataItem(MetaDataItem.Sidebar, null, Defaults.SidebarContent);
if(ssp.GetMetaDataItem(MetaDataItem.PageChangeMessage, null) == "")
ssp.SetMetaDataItem(MetaDataItem.PageChangeMessage, null, Defaults.PageChangeMessage);
if(ssp.GetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null) == "")
ssp.SetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null, Defaults.DiscussionChangeMessage);
if(ssp.GetMetaDataItem(MetaDataItem.ApproveDraftMessage, null) == "") {
ssp.SetMetaDataItem(MetaDataItem.ApproveDraftMessage, null, Defaults.ApproveDraftMessage);
}
// Load MIME Types
MimeTypes.Init();
// Load Providers
Collectors.FileNames = new System.Collections.Generic.Dictionary<string, string>(10);
Collectors.UsersProviderCollector = new ProviderCollector<IUsersStorageProviderV30>();
Collectors.PagesProviderCollector = new ProviderCollector<IPagesStorageProviderV30>();
Collectors.FilesProviderCollector = new ProviderCollector<IFilesStorageProviderV30>();
Collectors.FormatterProviderCollector = new ProviderCollector<IFormatterProviderV30>();
Collectors.CacheProviderCollector = new ProviderCollector<ICacheProviderV30>();
Collectors.DisabledUsersProviderCollector = new ProviderCollector<IUsersStorageProviderV30>();
Collectors.DisabledPagesProviderCollector = new ProviderCollector<IPagesStorageProviderV30>();
Collectors.DisabledFilesProviderCollector = new ProviderCollector<IFilesStorageProviderV30>();
Collectors.DisabledFormatterProviderCollector = new ProviderCollector<IFormatterProviderV30>();
Collectors.DisabledCacheProviderCollector = new ProviderCollector<ICacheProviderV30>();
// Load built-in providers
// Files storage providers have to be loaded BEFORE users storage providers in order to properly set permissions
FilesStorageProvider f = new FilesStorageProvider();
if(!ProviderLoader.IsDisabled(f.GetType().FullName)) {
f.Init(Host.Instance, "");
Collectors.FilesProviderCollector.AddProvider(f);
Log.LogEntry("Provider " + f.Information.Name + " loaded (Enabled)", EntryType.General, Log.SystemUsername);
}
else {
Collectors.DisabledFilesProviderCollector.AddProvider(f);
Log.LogEntry("Provider " + f.Information.Name + " loaded (Disabled)", EntryType.General, Log.SystemUsername);
}
UsersStorageProvider u = new UsersStorageProvider();
if(!ProviderLoader.IsDisabled(u.GetType().FullName)) {
u.Init(Host.Instance, "");
Collectors.UsersProviderCollector.AddProvider(u);
Log.LogEntry("Provider " + u.Information.Name + " loaded (Enabled)", EntryType.General, Log.SystemUsername);
}
else {
Collectors.DisabledUsersProviderCollector.AddProvider(u);
Log.LogEntry("Provider " + u.Information.Name + " loaded (Disabled)", EntryType.General, Log.SystemUsername);
}
// Load Users (pages storage providers might need access to users/groups data for upgrading from 2.0 to 3.0)
ProviderLoader.FullLoad(true, false, false, false, false);
//Users.Instance = new Users();
bool groupsCreated = VerifyAndCreateDefaultGroups();
PagesStorageProvider p = new PagesStorageProvider();
if(!ProviderLoader.IsDisabled(p.GetType().FullName)) {
p.Init(Host.Instance, "");
Collectors.PagesProviderCollector.AddProvider(p);
Log.LogEntry("Provider " + p.Information.Name + " loaded (Enabled)", EntryType.General, Log.SystemUsername);
}
else {
Collectors.DisabledPagesProviderCollector.AddProvider(p);
Log.LogEntry("Provider " + p.Information.Name + " loaded (Disabled)", EntryType.General, Log.SystemUsername);
}
CacheProvider c = new CacheProvider();
if(!ProviderLoader.IsDisabled(c.GetType().FullName)) {
c.Init(Host.Instance, "");
Collectors.CacheProviderCollector.AddProvider(c);
Log.LogEntry("Provider " + c.Information.Name + " loaded (Enabled)", EntryType.General, Log.SystemUsername);
}
else {
Collectors.DisabledCacheProviderCollector.AddProvider(c);
Log.LogEntry("Provider " + c.Information.Name + " loaded (Disabled)", EntryType.General, Log.SystemUsername);
}
// Load all other providers
ProviderLoader.FullLoad(false, true, true, true, true);
if(groupsCreated) {
// It is necessary to set default permissions for file management
UserGroup administratorsGroup = Users.FindUserGroup(Settings.AdministratorsGroup);
UserGroup anonymousGroup = Users.FindUserGroup(Settings.AnonymousGroup);
UserGroup usersGroup = Users.FindUserGroup(Settings.UsersGroup);
SetAdministratorsGroupDefaultPermissions(administratorsGroup);
SetUsersGroupDefaultPermissions(usersGroup);
SetAnonymousGroupDefaultPermissions(anonymousGroup);
}
// Init cache
//Cache.Instance = new Cache(Collectors.CacheProviderCollector.GetProvider(Settings.DefaultCacheProvider));
if(Collectors.CacheProviderCollector.GetProvider(Settings.DefaultCacheProvider) == null) {
Log.LogEntry("Default Cache Provider was not loaded, backing to integrated provider", EntryType.Error, Log.SystemUsername);
Settings.DefaultCacheProvider = typeof(CacheProvider).FullName;
Collectors.TryEnable(Settings.DefaultCacheProvider);
}
// Load Snippets and templates
//Snippets.Instance = new Snippets();
//Templates.Instance = new Templates();
// Load Pages
//Pages.Instance = new Pages();
// Load Nav. Paths
//NavigationPaths.Instance = new NavigationPaths();
// Create Collisions class
//Collisions.Instance = new Collisions();
// Create Redirections class
//Redirections.Instance = new Redirections();
// Create the Main Page, if needed
if(Pages.FindPage(Settings.DefaultPage) == null) CreateMainPage();
Log.LogEntry("ScrewTurn Wiki is ready", EntryType.General, Log.SystemUsername);
}
/// <summary>
/// Verifies the existence of the default user groups and creates them if necessary.
/// </summary>
/// <returns><c>true</c> if the groups were created, <c>false</c> otherwise.</returns>
private static bool VerifyAndCreateDefaultGroups() {
UserGroup administratorsGroup = Users.FindUserGroup(Settings.AdministratorsGroup);
UserGroup anonymousGroup = Users.FindUserGroup(Settings.AnonymousGroup);
UserGroup usersGroup = Users.FindUserGroup(Settings.UsersGroup);
// Create default groups if they don't exist already, initializing permissions
bool aGroupWasCreated = false;
if(administratorsGroup == null) {
Users.AddUserGroup(Settings.AdministratorsGroup, "Built-in Administrators");
administratorsGroup = Users.FindUserGroup(Settings.AdministratorsGroup);
aGroupWasCreated = true;
}
if(usersGroup == null) {
Users.AddUserGroup(Settings.UsersGroup, "Built-in Users");
usersGroup = Users.FindUserGroup(Settings.UsersGroup);
aGroupWasCreated = true;
}
if(anonymousGroup == null) {
Users.AddUserGroup(Settings.AnonymousGroup, "Built-in Anonymous Users");
anonymousGroup = Users.FindUserGroup(Settings.AnonymousGroup);
aGroupWasCreated = true;
}
if(aGroupWasCreated) {
ImportPageDiscussionPermissions();
}
return aGroupWasCreated;
}
/// <summary>
/// Creates the main page.
/// </summary>
private static void CreateMainPage() {
Pages.CreatePage(null as string, Settings.DefaultPage);
Pages.ModifyPage(Pages.FindPage(Settings.DefaultPage), "Main Page", Log.SystemUsername,
DateTime.Now, "", Defaults.MainPageContent, null, null, SaveMode.Normal);
}
/// <summary>
/// Performs shutdown operations, such as shutting-down Providers.
/// </summary>
public static void Shutdown() {
foreach(IFormatterProviderV30 provider in Collectors.FormatterProviderCollector.AllProviders) {
provider.Shutdown();
}
foreach(IPagesStorageProviderV30 provider in Collectors.PagesProviderCollector.AllProviders) {
provider.Shutdown();
}
foreach(IUsersStorageProviderV30 provider in Collectors.UsersProviderCollector.AllProviders) {
provider.Shutdown();
}
foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) {
provider.Shutdown();
}
foreach(ICacheProviderV30 provider in Collectors.CacheProviderCollector.AllProviders) {
provider.Shutdown();
}
Settings.Provider.Shutdown();
}
/// <summary>
/// Sets the default permissions for the administrators group, properly importing version 2.0 values.
/// </summary>
/// <param name="administrators">The administrators group.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
public static bool SetAdministratorsGroupDefaultPermissions(UserGroup administrators) {
// Administrators can do any operation
return AuthWriter.SetPermissionForGlobals(AuthStatus.Grant, Actions.FullControl, administrators);
// Settings.ConfigVisibleToAdmins is not imported on purpose
}
/// <summary>
/// Sets the default permissions for the users group, properly importing version 2.0 values.
/// </summary>
/// <param name="users">The users group.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
public static bool SetUsersGroupDefaultPermissions(UserGroup users) {
bool done = true;
// Set namespace-related permissions
if(Settings.UsersCanCreateNewPages) {
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.CreatePages, users);
}
else done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ModifyPages, users);
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.PostDiscussion, users);
if(Settings.UsersCanCreateNewCategories || Settings.UsersCanManagePageCategories) {
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ManageCategories, users);
}
done &= SetupFileManagementPermissions(users);
return done;
}
/// <summary>
/// Sets the default permissions for the anonymous users group, properly importing version 2.0 values.
/// </summary>
/// <param name="anonymous">The anonymous users group.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
public static bool SetAnonymousGroupDefaultPermissions(UserGroup anonymous) {
bool done = true;
// Properly import Private/Public Mode wiki
if(Settings.PrivateAccess) {
// Nothing to do, because without any explicit grant, Anonymous users cannot do anything
}
else if(Settings.PublicAccess) {
// Public access, allow modification and propagate file management permissions if they were allowed for anonymous users
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ModifyPages, anonymous);
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.DownloadAttachments, anonymous);
if(Settings.UsersCanCreateNewPages) {
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.CreatePages, anonymous);
}
if(Settings.UsersCanCreateNewCategories || Settings.UsersCanManagePageCategories) {
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ManageCategories, anonymous);
}
if(Settings.FileManagementInPublicAccessAllowed) {
SetupFileManagementPermissions(anonymous);
}
}
else {
// Standard configuration, only allow read permissions
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ReadPages, anonymous);
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ReadDiscussion, anonymous);
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.DownloadAttachments, anonymous);
foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) {
done &= AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, prov, "/", Actions.ForDirectories.DownloadFiles, anonymous);
}
}
return done;
}
/// <summary>
/// Sets file management permissions for the users or anonymous users group, importing version 2.0 values.
/// </summary>
/// <param name="group">The group.</param>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
private static bool SetupFileManagementPermissions(UserGroup group) {
bool done = true;
if(Settings.UsersCanViewFiles) {
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.DownloadAttachments, group);
foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) {
done &= AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, prov, "/", Actions.ForDirectories.DownloadFiles, group);
}
}
if(Settings.UsersCanUploadFiles) {
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.UploadAttachments, group);
foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) {
done &= AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, prov, "/", Actions.ForDirectories.UploadFiles, group);
done &= AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, prov, "/", Actions.ForDirectories.CreateDirectories, group);
}
}
if(Settings.UsersCanDeleteFiles) {
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.DeleteAttachments, group);
foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) {
done &= AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, prov, "/", Actions.ForDirectories.DeleteFiles, group);
done &= AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, prov, "/", Actions.ForDirectories.DeleteDirectories, group);
}
}
return done;
}
/// <summary>
/// Imports version 2.0 page discussion settings and properly propagates them to user groups and single pages, when needed.
/// </summary>
/// <returns><c>true</c> if the operation succeeded, <c>false</c> otherwise.</returns>
private static bool ImportPageDiscussionPermissions() {
// Notes
// Who can read pages, can read discussions
// Who can modify pages, can post messages and read discussions
// Who can manage pages, can manage discussions and post messages
// Possible values: page|normal|locked|public
string value = Settings.DiscussionPermissions.ToLowerInvariant();
UserGroup usersGroup = Users.FindUserGroup(Settings.UsersGroup);
UserGroup anonymousGroup = Users.FindUserGroup(Settings.AnonymousGroup);
bool done = true;
switch(value) {
case "page":
// Nothing to do
break;
case "normal":
// Allow Users to post messages
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.PostDiscussion, usersGroup);
break;
case "locked":
// Deny Users to post messages
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Deny, null, Actions.ForNamespaces.PostDiscussion, usersGroup);
break;
case "public":
// Allow Users and Anonymous Users to post messages
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.PostDiscussion, usersGroup);
done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.PostDiscussion, anonymousGroup);
break;
}
return true;
}
}
}

56
Core/SubjectInfo.cs Normal file
View file

@ -0,0 +1,56 @@

using System;
using System.Collections.Generic;
using System.Text;
namespace ScrewTurn.Wiki {
/// <summary>
/// Describes the subject of an ACL entry.
/// </summary>
public class SubjectInfo {
private string name;
private SubjectType type;
/// <summary>
/// Initializes a new instance of the <see cref="T:SubjectInfo" /> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="type">The type.</param>
public SubjectInfo(string name, SubjectType type) {
this.name = name;
this.type = type;
}
/// <summary>
/// Gets the name.
/// </summary>
public string Name {
get { return name; }
}
/// <summary>
/// Gets the type.
/// </summary>
public SubjectType Type {
get { return type; }
}
}
/// <summary>
/// Lists legal values for the type of a subject.
/// </summary>
public enum SubjectType {
/// <summary>
/// A user.
/// </summary>
User,
/// <summary>
/// A group.
/// </summary>
Group
}
}

96
Core/Templates.cs Normal file
View file

@ -0,0 +1,96 @@

using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Manages content templates.
/// </summary>
public static class Templates {
/// <summary>
/// Gets all the content templates.
/// </summary>
/// <returns>The content templates, sorted by name.</returns>
public static List<ContentTemplate> GetTemplates() {
List<ContentTemplate> result = new List<ContentTemplate>(20);
// Retrieve templates from all providers
foreach(IPagesStorageProviderV30 prov in Collectors.PagesProviderCollector.AllProviders) {
result.AddRange(prov.GetContentTemplates());
}
result.Sort(new ContentTemplateNameComparer());
return result;
}
/// <summary>
/// Finds a content template.
/// </summary>
/// <param name="name">The name of the template to find.</param>
/// <returns>The content template, or <c>null</c>.</returns>
public static ContentTemplate Find(string name) {
List<ContentTemplate> templates = GetTemplates();
int index = templates.BinarySearch(new ContentTemplate(name, "", null), new ContentTemplateNameComparer());
if(templates.Count > 0 && index >= 0) return templates[index];
else return null;
}
/// <summary>
/// Adds a new content template.
/// </summary>
/// <param name="name">The name of the template.</param>
/// <param name="content">The content of the template.</param>
/// <param name="provider">The target provider (<c>null</c> for the default provider).</param>
/// <returns><c>true</c> if the template is added, <c>false</c> otherwise.</returns>
public static bool AddTemplate(string name, string content, IPagesStorageProviderV30 provider) {
if(Find(name) != null) return false;
if(provider == null) provider = Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider);
ContentTemplate result = provider.AddContentTemplate(name, content);
if(result != null) Log.LogEntry("Content Template " + name + " created", EntryType.General, Log.SystemUsername);
else Log.LogEntry("Creation failed for Content Template " + name, EntryType.Error, Log.SystemUsername);
return result != null;
}
/// <summary>
/// Removes a content template.
/// </summary>
/// <param name="template">The template to remove.</param>
/// <returns><c>true</c> if the template is removed, <c>false</c> otherwise.</returns>
public static bool RemoveTemplate(ContentTemplate template) {
bool done = template.Provider.RemoveContentTemplate(template.Name);
if(done) Log.LogEntry("Content Template " + template.Name + " deleted", EntryType.General, Log.SystemUsername);
else Log.LogEntry("Deletion failed for Content Template " + template.Name, EntryType.Error, Log.SystemUsername);
return done;
}
/// <summary>
/// Modifies a content template.
/// </summary>
/// <param name="template">The template to modify.</param>
/// <param name="content">The new content of the template.</param>
/// <returns><c>true</c> if the template is modified, <c>false</c> otherwise.</returns>
public static bool ModifyTemplate(ContentTemplate template, string content) {
ContentTemplate result = template.Provider.ModifyContentTemplate(template.Name, content);
if(result != null) Log.LogEntry("Content Template " + template.Name + " updated", EntryType.General, Log.SystemUsername);
else Log.LogEntry("Update failed for Content Template " + template.Name, EntryType.Error, Log.SystemUsername);
return result != null;
}
}
}

573
Core/Tools.cs Normal file
View file

@ -0,0 +1,573 @@

using System;
using System.Configuration;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Web;
using System.Web.Security;
using System.Globalization;
using ScrewTurn.Wiki.PluginFramework;
using System.Reflection;
using System.Net;
namespace ScrewTurn.Wiki {
/// <summary>
/// Contains useful Tools.
/// </summary>
public static class Tools {
/// <summary>
/// Gets all the included files for the HTML Head, such as CSS, JavaScript and Icon pluginAssemblies, for a namespace.
/// </summary>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <returns>The includes.</returns>
public static string GetIncludes(string nspace) {
string theme = Settings.GetTheme(nspace);
string themePath = Settings.GetThemePath(nspace);
StringBuilder result = new StringBuilder(300);
string[] css = Directory.GetFiles(Settings.ThemesDirectory + theme, "*.css");
string firstChunk;
for(int i = 0; i < css.Length; i++) {
if(Path.GetFileName(css[i]).IndexOf("_") != -1) {
firstChunk = Path.GetFileName(css[i]).Substring(0, Path.GetFileName(css[i]).IndexOf("_")).ToLower(CultureInfo.CurrentCulture);
if(firstChunk.Equals("screen") || firstChunk.Equals("print") || firstChunk.Equals("all") ||
firstChunk.Equals("aural") || firstChunk.Equals("braille") || firstChunk.Equals("embossed") ||
firstChunk.Equals("handheld") || firstChunk.Equals("projection") || firstChunk.Equals("tty") || firstChunk.Equals("tv")) {
result.Append(@"<link rel=""stylesheet"" media=""" + firstChunk + @""" href=""" + themePath + Path.GetFileName(css[i]) + @""" type=""text/css"" />" + "\n");
}
else {
result.Append(@"<link rel=""stylesheet"" href=""" + themePath + Path.GetFileName(css[i]) + @""" type=""text/css"" />" + "\n");
}
}
else {
result.Append(@"<link rel=""stylesheet"" href=""" + themePath + Path.GetFileName(css[i]) + @""" type=""text/css"" />" + "\n");
}
}
string customEditorCss = Path.Combine(Settings.ThemesDirectory, theme);
customEditorCss = Path.Combine(customEditorCss, "Editor.css");
if(File.Exists(customEditorCss)) result.AppendFormat(@"<link rel=""stylesheet"" href=""Themes/{0}/Editor.css"" type=""text/css"" />" + "\n", theme);
else result.Append(@"<link rel=""stylesheet"" href=""Themes/Editor.css"" type=""text/css"" />" + "\n");
// OpenSearch
result.AppendFormat(@"<link rel=""search"" href=""Search.aspx?OpenSearch=1"" type=""application/opensearchdescription+xml"" title=""{1}"" />",
Settings.MainUrl, Settings.WikiTitle + " - Search");
string[] js = Directory.GetFiles(Settings.ThemesDirectory + theme, "*.js");
for(int i = 0; i < js.Length; i++) {
result.Append(@"<script src=""" + themePath + Path.GetFileName(js[i]) + @""" type=""text/javascript""></script>" + "\n");
}
string[] icons = Directory.GetFiles(Settings.ThemesDirectory + theme, "Icon.*");
if(icons.Length > 0) {
result.Append(@"<link rel=""shortcut icon"" href=""" + themePath + Path.GetFileName(icons[0]) + @""" type=""");
switch(Path.GetExtension(icons[0]).ToLowerInvariant()) {
case ".ico":
result.Append("image/x-icon");
break;
case ".gif":
result.Append("image/gif");
break;
case ".png":
result.Append("image/png");
break;
}
result.Append(@""" />" + "\n");
}
result.Append(GetJavaScriptIncludes());
// Include HTML Head
result.Append(Settings.Provider.GetMetaDataItem(MetaDataItem.HtmlHead, null));
return result.ToString();
}
/// <summary>
/// Gets all the JavaScript files to include.
/// </summary>
/// <returns>The JS files.</returns>
public static string GetJavaScriptIncludes() {
StringBuilder buffer = new StringBuilder(100);
foreach(string js in Directory.GetFiles(Settings.JsDirectory, "*.js")) {
buffer.Append(@"<script type=""text/javascript"" src=""" + Settings.JsDirectoryName + "/" + Path.GetFileName(js) + @"""></script>" + "\n");
}
return buffer.ToString();
}
/// <summary>
/// Converts a byte number into a string, formatted using KB, MB or GB.
/// </summary>
/// <param name="bytes">The # of bytes.</param>
/// <returns>The formatted string.</returns>
public static string BytesToString(long bytes) {
if(bytes < 1024) return bytes.ToString() + " B";
else if(bytes < 1048576) return string.Format("{0:N2} KB", (float)bytes / 1024F);
else if(bytes < 1073741824) return string.Format("{0:N2} MB", (float)bytes / 1048576F);
else return string.Format("{0:N2} GB", (float)bytes / 1073741824F);
}
/// <summary>
/// Computes the Disk Space Usage of a directory.
/// </summary>
/// <param name="dir">The directory.</param>
/// <returns>The used Disk Space, in bytes.</returns>
public static long DiskUsage(string dir) {
string[] files = Directory.GetFiles(dir);
string[] directories = Directory.GetDirectories(dir);
long result = 0;
FileInfo file;
for(int i = 0; i < files.Length; i++) {
file = new FileInfo(files[i]);
result += file.Length;
}
for(int i = 0; i < directories.Length; i++) {
result += DiskUsage(directories[i]);
}
return result;
}
/// <summary>
/// Generates the standard 5-digit Page Version string.
/// </summary>
/// <param name="version">The Page version.</param>
/// <returns>The 5-digit Version string.</returns>
public static string GetVersionString(int version) {
string result = version.ToString();
int len = result.Length;
for(int i = 0; i < 5 - len; i++) {
result = "0" + result;
}
return result;
}
/// <summary>
/// Gets the available Themes.
/// </summary>
public static string[] AvailableThemes {
get {
string[] dirs = Directory.GetDirectories(Settings.ThemesDirectory);
string[] res = new string[dirs.Length];
for(int i = 0; i < dirs.Length; i++) {
//if(dirs[i].EndsWith("\\")) dirs[i] = dirs[i].Substring(0, dirs[i].Length - 1);
dirs[i] = dirs[i].TrimEnd(Path.DirectorySeparatorChar);
res[i] = dirs[i].Substring(dirs[i].LastIndexOf(Path.DirectorySeparatorChar) + 1);
}
return res;
}
}
/// <summary>
/// Gets the available Cultures.
/// </summary>
public static string[] AvailableCultures {
get {
// It seems, at least in VS 2008, that for Precompiled Web Sites, the GlobalResources pluginAssemblies that are not the
// default resource (Culture=neutral), get sorted into subdirectories named by the Culture Info name. Every
// assembly in these directories is called "ScrewTurn.Wiki.resources.dll"
// I'm sure it's possible to just use the subdirectory names in the bin directory to get the culture info names,
// however, I'm not sure what other things might get tossed in there by the compiler now or in the future.
// That's why I'm specifically going for the App_GlobalResources.resources.dlls.
// So, get all of the App_GlobalResources.resources.dll pluginAssemblies from bin and recurse subdirectories
string[] dllFiles = Directory.GetFiles(Path.Combine(Settings.RootDirectory, "bin"), "ScrewTurn.Wiki.resources.dll", SearchOption.AllDirectories);
// List to collect constructed culture names
List<string> cultureNames = new List<string>();
// Manually add en-US culture
CultureInfo enCI = new CultureInfo("en-US");
cultureNames.Add(enCI.Name + "|" + UppercaseInitial(enCI.NativeName) + " - " + enCI.EnglishName);
// For every file we find
// List format: xx-ZZ|Native name (English name)
foreach(string s in dllFiles) {
try {
// Load a reflection only assembly from the filename
Assembly asm = Assembly.ReflectionOnlyLoadFrom(s);
// string for destructive parsing of the assembly's full name
// Which, btw, looks something like this
// App_GlobalResources.resources, Version=0.0.0.0, Culture=zh-cn, PublicKeyToken=null
string fullName = asm.FullName;
// Find the Culture= attribute
int find = fullName.IndexOf("Culture=");
// Remove it and everything prior
fullName = fullName.Substring(find + 8);
// Find the trailing comma
find = fullName.IndexOf(',');
// Remove it and everything after
fullName = fullName.Substring(0, find);
// Fullname should now be the culture info name and we can instantiate the CultureInfo class from it
CultureInfo ci = new CultureInfo(fullName);
// StringBuilders
StringBuilder sb = new StringBuilder();
sb.Append(ci.Name);
sb.Append("|");
sb.Append(UppercaseInitial(ci.NativeName));
sb.Append(" - ");
sb.Append(ci.EnglishName);
// Add the newly constructed Culture string
cultureNames.Add(sb.ToString());
}
catch(Exception ex) {
Log.LogEntry("Error parsing culture info from " + s + Environment.NewLine + ex.Message, EntryType.Error, Log.SystemUsername);
}
}
// If for whatever reason every one fails, this will return a 1 element array with the en-US info.
cultureNames.Sort();
return cultureNames.ToArray();
}
}
private static string UppercaseInitial(string value) {
if(value.Length > 0) {
return value[0].ToString().ToUpper(CultureInfo.CurrentCulture) + value.Substring(1);
}
else return "";
}
/// <summary>
/// Computes the Hash of a Username, mixing it with other data, in order to avoid illegal Account activations.
/// </summary>
/// <param name="username">The Username.</param>
/// <param name="email">The email.</param>
/// <param name="dateTime">The date/time.</param>
/// <returns>The secured Hash of the Username.</returns>
public static string ComputeSecurityHash(string username, string email, DateTime dateTime) {
return Hash.ComputeSecurityHash(username, email, dateTime, Settings.MasterPassword);
}
/// <summary>
/// Escapes bad characters in a string (pipes and \n).
/// </summary>
/// <param name="input">The input string.</param>
/// <returns>The escaped string.</returns>
public static string EscapeString(string input) {
StringBuilder sb = new StringBuilder(input);
sb.Replace("\r", "");
sb.Replace("\n", "%0A");
sb.Replace("|", "%7C");
return sb.ToString();
}
/// <summary>
/// Unescapes bad characters in a string (pipes and \n).
/// </summary>
/// <param name="input">The input string.</param>
/// <returns>The unescaped string.</returns>
public static string UnescapeString(string input) {
StringBuilder sb = new StringBuilder(input);
sb.Replace("%7C", "|");
sb.Replace("%0A", "\n");
return sb.ToString();
}
/// <summary>
/// Generates a random 10-char Password.
/// </summary>
/// <returns>The Password.</returns>
public static string GenerateRandomPassword() {
Random r = new Random();
string password = "";
for(int i = 0; i < 10; i++) {
if(i % 2 == 0)
password += ((char)r.Next(65, 91)).ToString(); // Uppercase letter
else password += ((char)r.Next(97, 123)).ToString(); // Lowercase letter
}
return password;
}
/// <summary>
/// Gets the approximate System Uptime.
/// </summary>
public static TimeSpan SystemUptime {
get {
int t = Environment.TickCount;
if(t < 0) t = t + int.MaxValue;
t = t / 1000;
return TimeSpan.FromSeconds(t);
}
}
/// <summary>
/// Converts a Time Span to string.
/// </summary>
/// <param name="span">The Time Span.</param>
/// <returns>The string.</returns>
public static string TimeSpanToString(TimeSpan span) {
string result = span.Days.ToString() + "d ";
result += span.Hours.ToString() + "h ";
result += span.Minutes.ToString() + "m ";
result += span.Seconds.ToString() + "s";
return result;
}
/// <summary>
/// Executes URL-encoding, avoiding to use '+' for spaces.
/// </summary>
/// <param name="input">The input string.</param>
/// <returns>The encoded string.</returns>
public static string UrlEncode(string input) {
return HttpContext.Current.Server.UrlEncode(input).Replace("+", "%20");
}
/// <summary>
/// Executes URL-decoding, replacing spaces as processed by UrlEncode.
/// </summary>
/// <param name="input">The input string.</param>
/// <returns>The decoded string.</returns>
public static string UrlDecode(string input) {
return HttpContext.Current.Server.UrlDecode(input.Replace("%20", " "));
}
/// <summary>
/// Removes all HTML tags from a text.
/// </summary>
/// <param name="html">The input HTML.</param>
/// <returns>The extracted plain text.</returns>
public static string RemoveHtmlMarkup(string html) {
StringBuilder sb = new StringBuilder(System.Text.RegularExpressions.Regex.Replace(html, "<[^>]*>", " "));
sb.Replace("&nbsp;", " ");
sb.Replace(" ", " ");
return sb.ToString();
}
/// <summary>
/// Extracts the directory name from a path used in the Files Storage Providers.
/// </summary>
/// <param name="path">The path, for example '/folder/blah/'.</param>
/// <returns>The directory name, for example 'blah'.</returns>
public static string ExtractDirectoryName(string path) {
path = path.Trim('/');
int idx = path.LastIndexOf("/");
return idx != -1 ? path.Substring(idx + 1) : path;
}
/// <summary>
/// Detects the correct <see cref="T:PageInfo" /> object associated to the current page using the <b>Page</b> and <b>NS</b> parameters in the query string.
/// </summary>
/// <param name="loadDefault"><c>true</c> to load the default page of the specified namespace when <b>Page</b> is not specified, <c>false</c> otherwise.</param>
/// <returns>If <b>Page</b> is specified and exists, the correct <see cref="T:PageInfo" />, otherwise <c>null</c> if <b>loadDefault</b> is <c>false</c>,
/// or the <see cref="T:PageInfo" /> object representing the default page of the specified namespace if <b>loadDefault</b> is <c>true</c>.</returns>
public static PageInfo DetectCurrentPageInfo(bool loadDefault) {
string nspace = HttpContext.Current.Request["NS"];
NamespaceInfo nsinfo = nspace != null ? Pages.FindNamespace(nspace) : null;
string page = HttpContext.Current.Request["Page"];
if(string.IsNullOrEmpty(page)) {
if(loadDefault) {
if(nsinfo == null) page = Settings.DefaultPage;
else page = nsinfo.DefaultPage != null ? nsinfo.DefaultPage.FullName : "";
}
else return null;
}
string fullName = null;
if(!page.StartsWith(nspace + ".")) fullName = nspace + "." + page;
else fullName = page;
fullName = fullName.Trim('.');
return Pages.FindPage(fullName);
}
/// <summary>
/// Detects the full name of the current page using the <b>Page</b> and <b>NS</b> parameters in the query string.
/// </summary>
/// <returns>The full name of the page, regardless of the existence of the page.</returns>
public static string DetectCurrentFullName() {
string nspace = HttpContext.Current.Request["NS"] != null ? HttpContext.Current.Request["NS"] : "";
string page = HttpContext.Current.Request["Page"] != null ? HttpContext.Current.Request["Page"] : "";
string fullName = null;
if(!page.StartsWith(nspace + ".")) fullName = nspace + "." + page;
else fullName = page;
return fullName.Trim('.');
}
/// <summary>
/// Detects the correct <see cref="T:NamespaceInfo" /> object associated to the current namespace using the <b>NS</b> parameter in the query string.
/// </summary>
/// <returns>The correct <see cref="T:NamespaceInfo" /> object, or <c>null</c>.</returns>
public static NamespaceInfo DetectCurrentNamespaceInfo() {
string nspace = HttpContext.Current.Request["NS"];
NamespaceInfo nsinfo = nspace != null ? Pages.FindNamespace(nspace) : null;
return nsinfo;
}
/// <summary>
/// Detects the name of the current namespace using the <b>NS</b> parameter in the query string.
/// </summary>
/// <returns>The name of the namespace, or an empty string.</returns>
public static string DetectCurrentNamespace() {
return HttpContext.Current.Request["NS"] != null ? HttpContext.Current.Request["NS"] : "";
}
/// <summary>
/// Gets the message ID for HTML anchors.
/// </summary>
/// <param name="messageDateTime">The message date/time.</param>
/// <returns>The ID.</returns>
public static string GetMessageIdForAnchor(DateTime messageDateTime) {
return "MSG_" + messageDateTime.ToString("yyyyMMddHHmmss");
}
/// <summary>
/// Gets the name of a file's directory.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>The name of the item.</returns>
public static string GetDirectoryName(string filename) {
if(filename != null) {
int index = filename.LastIndexOf("/");
if(index > 0) {
string directoryName = filename.Substring(0, index + 1);
if(!directoryName.StartsWith("/")) directoryName = "/" + directoryName;
return directoryName;
}
}
// Assume to navigate in the root directory
return "/";
}
/// <summary>
/// Gets the update status of a component.
/// </summary>
/// <param name="url">The version file URL.</param>
/// <param name="currentVersion">The current version.</param>
/// <param name="newVersion">The new version, if any.</param>
/// <param name="newAssemblyUrl">The URL of the new assembly, if applicable and available.</param>
/// <returns>The update status.</returns>
/// <remarks>This method only works in Full Trust.</remarks>
public static UpdateStatus GetUpdateStatus(string url, string currentVersion, out string newVersion, out string newAssemblyUrl) {
// TODO: Verify usage of WebPermission class
// http://msdn.microsoft.com/en-us/library/system.net.webpermission.aspx
string urlHash = "UpdUrlCache-" + url.GetHashCode().ToString();
try {
string ver = null;
if(HttpContext.Current != null) {
ver = HttpContext.Current.Cache[urlHash] as string;
}
if(ver == null) {
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.AllowAutoRedirect = true;
HttpWebResponse res = (HttpWebResponse)req.GetResponse();
if(res.StatusCode != HttpStatusCode.OK) {
newVersion = null;
newAssemblyUrl = null;
return UpdateStatus.Error;
}
StreamReader sr = new StreamReader(res.GetResponseStream());
ver = sr.ReadToEnd();
sr.Close();
if(HttpContext.Current != null) {
HttpContext.Current.Cache.Add(urlHash, ver, null, DateTime.Now.AddMinutes(5),
System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
}
}
string[] lines = ver.Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
if(lines.Length == 0) {
newVersion = null;
newAssemblyUrl = null;
return UpdateStatus.Error;
}
string[] versions = lines[0].Split('|');
bool upToDate = false;
for(int i = 0; i < versions.Length; i++) {
ver = versions[i];
if(versions[i].Equals(currentVersion)) {
if(i == versions.Length - 1) upToDate = true;
else upToDate = false;
ver = versions[versions.Length - 1];
break;
}
}
if(upToDate) {
newVersion = null;
newAssemblyUrl = null;
return UpdateStatus.UpToDate;
}
else {
newVersion = ver;
if(lines.Length == 2) newAssemblyUrl = lines[1];
else newAssemblyUrl = null;
return UpdateStatus.NewVersionFound;
}
}
catch(Exception) {
if(HttpContext.Current != null) {
HttpContext.Current.Cache.Add(urlHash, "", null, DateTime.Now.AddMinutes(5),
System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
}
newVersion = null;
newAssemblyUrl = null;
return UpdateStatus.Error;
}
}
/// <summary>
/// Computes the hash value of a string that is value across application instances and versions.
/// </summary>
/// <param name="value">The string to compute the hash of.</param>
/// <returns>The hash value.</returns>
public static uint HashDocumentNameForTemporaryIndex(string value) {
if(value == null) throw new ArgumentNullException("value");
// sdbm algorithm, borrowed from http://www.cse.yorku.ca/~oz/hash.html
uint hash = 0;
foreach(char c in value) {
// hash(i) = hash(i - 1) * 65599 + str[i]
hash = c + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
}
/// <summary>
/// Lists legal update statuses.
/// </summary>
public enum UpdateStatus {
/// <summary>
/// Error while retrieving version information.
/// </summary>
Error,
/// <summary>
/// The component is up-to-date.
/// </summary>
UpToDate,
/// <summary>
/// A new version was found.
/// </summary>
NewVersionFound
}
}

308
Core/Translator.cs Normal file
View file

@ -0,0 +1,308 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace ScrewTurn.Wiki.ImportWiki {
/// <summary>
/// Implements a translator tool for importing MediaWiki data.
/// </summary>
public class Translator : ScrewTurn.Wiki.ImportWiki.ITranslator {
private Regex noWiki = new Regex(@"<nowiki>(.|\n|\r)*?</nowiki>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Executes the translation.
/// </summary>
/// <param name="input">The input content.</param>
/// <returns>The WikiMarkup.</returns>
public string Translate(string input) {
StringBuilder sb = new StringBuilder();
sb.Append(input);
Regex firsttitle = new Regex(@"(\={2,5}).+?\1");
Regex doubleSquare = new Regex(@"\[{2}.+?\]{2}");
Regex exlink = new Regex(@"(\[(\w+):\/\/([^\]]+)\])|(?<!\"")((?<Protocol>\w+):\/\/(?<Domain>[\w.]+\/?)\S*)(?!\"")");
Regex mailto = new Regex(@"(\[mailto\:([\w\d\.\@]+)\])|(?<!\"")(mailto\:([\w\d\.\@]+))(?!\"")");
Regex image = new Regex(@"\[Image\:.+?\]");
Regex pre = new Regex(@"<pre>((.|\n|\r)*?</pre>)?");
Regex newlinespace = new Regex(@"^\ .+", RegexOptions.Multiline);
Regex transclusion = new Regex(@"(\{{2})(.|\n)+?(\}{2})");
Regex math = new Regex(@"<math>(.|\n|\r)*?</math>");
Regex references = new Regex(@"<ref>(.|\n|\r)*?</ref>");
Regex references1 = new Regex(@"\<references\/\>");
Regex redirect = new Regex(@"\#REDIRECT\ \[{2}.+\]{2}");
Match match;
List<int> noWikiBegin = new List<int>(), noWikiEnd = new List<int>();
/*ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
if (sb.ToString().Contains("__NOTOC__"))
{
sb.Remove(sb.ToString().IndexOf("__NOTOC__"), 9);
if (sb.ToString().Contains("{{compactTOC}}"))
{
sb.Remove(sb.ToString().IndexOf("{{compactTOC}}"),14);
sb.Insert(sb.ToString().IndexOf("{{compactTOC}}"), "\n{TOC}\n");
}
}
else
{
match = firsttitle.Match(sb.ToString());
if(match.Success)
sb.Insert(match.Index, "\n{TOC}\n");
}*/
sb.Replace("\r", "");
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
///////////////////////////////////////////////
//////BEGIN of unsupported formatting-tag//////
///////////////////////////////////////////////
match = math.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) {
sb.Replace(match.Value, @"<span style=""background-color:red; color:white""><nowiki><esc>&lt;math&gt;" + match.Value.Substring(6, match.Length - 13) + @"&lt;/math&gt;</esc></nowiki></span> ");
match = math.Match(sb.ToString());
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
}
else
match = math.Match(sb.ToString(), end);
}
match = references.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) {
sb.Replace(match.Value, @"<span style=""background-color:red; color:white""><nowiki><esc>&lt;ref&gt;" + match.Value.Substring(5, match.Length - 11) + @"&lt;/ref&gt;</esc></nowiki></span> ");
match = references.Match(sb.ToString());
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
}
else
match = references.Match(sb.ToString(), end);
}
match = references1.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) {
sb.Replace(match.Value, @"<span style=""background-color:red; color:white""><nowiki><esc>&lt;references/&gt;</esc></nowiki></span> ");
match = references1.Match(sb.ToString());
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
}
else
match = references1.Match(sb.ToString(), end);
}
match = redirect.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) {
sb.Replace(match.Value, @"<span style=""background-color:red; color:white""><nowiki><esc>" + match.Value + @"</esc></nowiki></span> ");
match = redirect.Match(sb.ToString());
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
}
else
match = redirect.Match(sb.ToString(), end);
}
match = transclusion.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) {
string s = @"<span style=""background-color:red; color:white""><nowiki>" + match.Value + @"</nowiki></span> ";
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, s);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = transclusion.Match(sb.ToString(), match.Index + s.Length);
}
match = transclusion.Match(sb.ToString(), end);
}
//////////////////////////////////////////
/////END of unsupported formetter-tag/////
//////////////////////////////////////////
match = pre.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) {
string s = "{{{{<nowiki>" + match.Value.Replace("<pre>", "").Replace("</pre>", "") + @"</nowiki>}}}}";
sb.Replace(match.Value, s);
match = pre.Match(sb.ToString(), match.Index + s.Length);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
}
else
match = pre.Match(sb.ToString(), end);
}
match = exlink.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) {
if(match.Value[0] == '[') {
string[] split = match.Value.Split(new char[] { ' ' }, 2);
if(split.Length == 2) {
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, split[0] + "|" + split[1]);
match = exlink.Match(sb.ToString(), match.Index + match.Length);
}
else
match = exlink.Match(sb.ToString(), match.Index + match.Length);
}
else {
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, "[" + match.Value + "]");
match = exlink.Match(sb.ToString(), match.Index + match.Length + 1);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
}
}
else
match = exlink.Match(sb.ToString(), end);
}
match = mailto.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) {
string str = match.Value.Replace(@"mailto:", "");
if(str[0] == '[') {
string[] split = str.Split(new char[] { ' ' }, 2);
if(split.Length == 2) {
sb.Remove(match.Index, match.Length);
sb.Insert(match.Length, split[0] + "|" + split[1]);
}
/*else
{
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, split[0]);
}*/
match = mailto.Match(sb.ToString(), match.Index + str.Length);
}
else {
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, "[" + str.Substring(0, str.Length) + "]");
match = mailto.Match(sb.ToString(), match.Index + str.Length + 1);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
}
}
else
match = mailto.Match(sb.ToString(), end);
}
match = image.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) {
string str = match.Value.Remove(0, 7);
str = str.Remove(str.Length - 1);
string name = "";
string location = "";
string caption = "";
string[] splits = str.Split(new char[] { '|' });
name = splits[0];
for(int i = 0; i < splits.Length; i++) {
if(splits[i] == "right" || splits[i] == "left" || splits[i] == "center" || splits[i] == "none")
location = splits[i];
else if(splits[i].Contains("px")) { }
else if(splits[i] == "thumb" || splits[i] == "thumbnail" || splits[i] == "frame") { }
else
caption = splits[i] + "|";
}
if(location == "right" || location == "left") {
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, "[image" + location + "|" + caption + "{UP}" + name + "]");
}
else {
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, "[imageauto|" + caption + "{UP}" + name + "]");
}
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
}
match = image.Match(sb.ToString(), end);
}
match = doubleSquare.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) {
if(match.Value.Contains(":")) {
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, match.Value.Replace(':', '_').Replace("/", "_").Replace(@"\", "_").Replace('?', '_'));
}
else {
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, match.Value.Substring(1, match.Length - 2).Replace("/", "_").Replace(@"\", "_").Replace('?', '_'));
}
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
}
match = doubleSquare.Match(sb.ToString(), end);
}
bool first = true;
match = newlinespace.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) {
string s = "";
if(first)
s += "{{{{" + match.Value.Substring(1, match.Value.Length - 1);
else
s += match.Value.Substring(1, match.Value.Length - 1);
if(sb.Length > match.Index + match.Length + 1) {
if(sb[match.Index + match.Length] == '\n' && sb[match.Index + match.Length + 1] == ' ') {
first = false;
}
else {
s += "}}}}";
first = true;
}
}
else {
s += "}}}}";
first = true;
}
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, s);
match = newlinespace.Match(sb.ToString(), match.Index + s.Length);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
}
else
match = newlinespace.Match(sb.ToString(), end);
}
return sb.ToString();
}
private void ComputeNoWiki(string text, ref List<int> noWikiBegin, ref List<int> noWikiEnd) {
Match match;
noWikiBegin.Clear();
noWikiEnd.Clear();
match = noWiki.Match(text);
while(match.Success) {
noWikiBegin.Add(match.Index);
noWikiEnd.Add(match.Index + match.Length - 1);
match = noWiki.Match(text, match.Index + match.Length);
}
}
private bool IsNoWikied(int index, List<int> noWikiBegin, List<int> noWikiEnd, out int end) {
for(int i = 0; i < noWikiBegin.Count; i++) {
if(index >= noWikiBegin[i] && index <= noWikiEnd[i]) {
end = noWikiEnd[i];
return false;
}
}
end = 0;
return true;
}
}
}

419
Core/TranslatorFlex.cs Normal file
View file

@ -0,0 +1,419 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace ScrewTurn.Wiki.ImportWiki {
/// <summary>
/// Implements a translator tool for importing FlexWiki data.
/// </summary>
public class TranslatorFlex : ScrewTurn.Wiki.ImportWiki.ITranslator {
private Regex noWiki = new Regex(@"\<nowiki\>(.|\s)+?\<\/nowiki\>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private Regex noFlex = new Regex(@"\<noflex\>(.|\s)+?\<\/noflex\>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Executes the translation.
/// </summary>
/// <param name="input">The input content.</param>
/// <returns>The WikiMarkup.</returns>
public string Translate(string input) {
StringBuilder sb = new StringBuilder();
sb.Append(input);
List<int> noWikiBegin = new List<int>(), noWikiEnd = new List<int>(), noFlexBegin = new List<int>(), noFlexEnd = new List<int>();
Match match;
Regex doubleDoubleBrackets = new Regex(@"\""{2}.+?\""{2}");
Regex italic = new Regex(@"_.+?_");
Regex bold = new Regex(@"\*.+?\*");
Regex underline = new Regex(@"\+.+?\+");
Regex strikethrough = new Regex(@"\-.*?\-");
Regex head = new Regex(@"^!{1,6}.+", RegexOptions.Multiline);
Regex wikiTalk = new Regex(@"@@.+?@@");
Regex code = new Regex(@"@.+?@");
Regex pascalCase = new Regex(@"(\""[^""]+?\""\:)?([A-Z][a-z]+){2,}(\.([A-Z][a-z]+){2,})?");
Regex camelCase = new Regex(@"(\""[^""]+?\""\:)?(([A-Z][a-z]+){2,}\.)?\[.+?\]");
Regex exlink = new Regex(@"(\""[^""]+?\""\:)?(?<Protocol>\w+):\/\/(?<Domain>[\w.]+\/?)\S*");
Regex email = new Regex(@"(\""[^""]+?\""\:)?mailto\:.+");
Regex lists = new Regex(@"^(\t|\s{8})+(\*|(1\.))", RegexOptions.Multiline);
Regex table = new Regex(@"^\|{2}", RegexOptions.Multiline);
Regex newlinespace = new Regex(@"^\ .+", RegexOptions.Multiline);
sb.Replace("\r", "");
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = doubleDoubleBrackets.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = doubleDoubleBrackets.Match(sb.ToString(), end);
else {
string s = "<nowiki>" + match.Value.Substring(2, match.Length - 4) + "</nowiki>";
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, s);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = doubleDoubleBrackets.Match(sb.ToString(), match.Index + s.Length);
}
}
match = exlink.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = exlink.Match(sb.ToString(), end);
else {
string s;
string[] split = match.Value.Split(new char[] { '"' });
if(split.Length == 1) {
if(match.Value.EndsWith(".jpeg", StringComparison.CurrentCultureIgnoreCase) | match.Value.EndsWith(".gif", StringComparison.CurrentCultureIgnoreCase) | match.Value.EndsWith(".jpg", StringComparison.CurrentCultureIgnoreCase)) {
string imgName = match.Value.Substring(match.Value.LastIndexOf('/') + 1, match.Length - match.Value.LastIndexOf('/') - 1);
s = "<noflex>[image|" + imgName + "|{UP}" + imgName + "]</noflex>";
}
else s = "[" + match.Value + "]";
}
else {
if(split[1].EndsWith(".jpeg", StringComparison.CurrentCultureIgnoreCase) | split[1].EndsWith(".gif", StringComparison.CurrentCultureIgnoreCase) | split[1].EndsWith(".jpg", StringComparison.CurrentCultureIgnoreCase)) {
string imgName = split[1].Substring(split[1].LastIndexOf('/') + 1, split[1].Length - split[1].LastIndexOf('/') - 1);
s = "<noflex>[image|" + imgName + "|{UP}" + imgName + "|" + split[2].Substring(1, split[2].Length - 1) + "]</noflex>";
}
else s = "<noflex>[" + split[2].Substring(1, split[2].Length - 1) + "|" + split[1] + "]</noflex>";
}
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, s);
ComputeNoFlex(sb.ToString(), ref noFlexBegin, ref noFlexEnd);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = exlink.Match(sb.ToString(), match.Index + s.Length);
}
}
match = email.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = email.Match(sb.ToString(), end);
else {
string mailLink = "";
if(match.Value.StartsWith("mailto:")) mailLink = "[" + match.Value.Replace("mailto:", "") + "]";
else mailLink = "<noflex>[" + match.Value.Split(new char[] { ':' }, 2)[1].Replace("mailto:", "") + "|" + match.Value.Split(new char[] { ':' }, 2)[0].Substring(1, match.Value.Split(new char[] { ':' }, 2)[0].Length - 2) + "]</noflex>";
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, mailLink);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = email.Match(sb.ToString(), match.Index + mailLink.Length);
}
}
match = bold.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = bold.Match(sb.ToString(), end);
else {
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, "'''" + match.Value.Substring(1, match.Length - 2) + @"'''");
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = bold.Match(sb.ToString());
}
}
match = underline.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = underline.Match(sb.ToString(), end);
else {
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, "__" + match.Value.Substring(1, match.Length - 2) + @"__");
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = underline.Match(sb.ToString());
}
}
match = strikethrough.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = strikethrough.Match(sb.ToString(), end);
else {
string s = "";
if(match.Value == "---") s = "---";
else if(match.Value == "--") s = "-";
else s = "--" + match.Value.Substring(1, match.Length - 2) + @"--";
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, s);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = strikethrough.Match(sb.ToString(), match.Index + s.Length);
}
}
match = head.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = head.Match(sb.ToString(), end);
else {
//Count the number of ! in order to put a corresponding
//number of =
int count = 1;
for(int i = 0; i < match.Length; i++) {
if(match.Value[i] == '!')
count++;
}
if(match.Value.EndsWith("!")) count--;
string s = "";
for(int i = 0; i < count; i++)
s += "=";
s += match.Value.Substring(count - 1, match.Length - (count - 1) - 1);
for(int i = 0; i < count; i++)
s += "=";
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, s);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = head.Match(sb.ToString());
}
}
match = wikiTalk.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noFlexBegin, noFlexEnd, out end))
match = wikiTalk.Match(sb.ToString(), end);
else {
string s = @"<span style=""background-color:red; color:white""><nowiki>" + match.Value + @"</nowiki></span>";
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, s);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = wikiTalk.Match(sb.ToString(), match.Index + s.Length);
}
}
match = code.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = code.Match(sb.ToString(), end);
else {
string s = "{{<nowiki>" + match.Value.Substring(1, match.Length - 2) + @"</nowiki>}}";
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, s);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = code.Match(sb.ToString());
}
}
match = camelCase.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = camelCase.Match(sb.ToString(), end);
else {
string s;
string[] split = match.Value.Split(new char[] { ':' }, 2);
if(split.Length == 1) {
string[] split1 = match.Value.Split(new char[] { '.' }, 2);
if(split1.Length == 1) s = match.Value;
else s = split1[1];
}
else {
string[] split1 = split[1].Split(new char[] { '.' }, 2);
if(split1.Length == 1) s = "[" + split[1].Substring(1, split[1].Length - 2) + "|" + split[0].Substring(1, split[0].Length - 2) + "]";
else s = "[" + split1[1].Substring(0, split1[1].Length - 1) + "|" + split[0].Substring(1, split[0].Length - 2) + "]";
}
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, s);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = camelCase.Match(sb.ToString(), match.Index + s.Length);
}
}
match = pascalCase.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = pascalCase.Match(sb.ToString(), end);
else {
string s;
string[] split = match.Value.Split(new char[] { ':' }, 2);
if(split.Length == 1) {
string[] split1 = match.Value.Split(new char[] { '.' }, 2);
if(split1.Length == 1) s = "[" + match.Value + "]";
else s = "[" + split1[1] + "]";
}
else {
string[] split1 = split[1].Split(new char[] { '.' });
if(split1.Length == 1) s = "[" + split[1] + "|" + split[0].Substring(1, split[0].Length - 2) + "]";
else s = "[" + split1[1] + "|" + split[0].Substring(1, split[0].Length - 2) + "]";
}
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, s);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = pascalCase.Match(sb.ToString(), match.Index + s.Length);
}
}
match = lists.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = lists.Match(sb.ToString(), end);
else {
string s = "";
char kindOfList;
int i = 0;
char[] ca = match.Value.ToCharArray();
while(ca[i] == ' ')
i++;
if(i > 0) {
kindOfList = ca[i];
i = i / 8;
}
else {
while(ca[i] == '\t')
i++;
kindOfList = ca[i];
}
for(int k = 1; k <= i; k++) {
if(kindOfList == '*') s += "*";
if(kindOfList == '1') s += "#";
}
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, s);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = lists.Match(sb.ToString(), match.Index + s.Length);
}
}
bool tableBegin = true;
match = table.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = table.Match(sb.ToString(), end);
else {
string s = "";
int indexEndOfLine = sb.ToString().IndexOf("\r\n", match.Index);
if(indexEndOfLine < 0) indexEndOfLine = sb.Length;
string[] split = sb.ToString().Substring(match.Index + 2, indexEndOfLine - match.Index - 4).Split(new string[] { "||" }, StringSplitOptions.None);
if(tableBegin) s = "{|\r\n| " + split[0];
else s = "|-\r\n| " + split[0];
for(int i = 1; i < split.Length; i++)
s += " || " + split[i];
if(indexEndOfLine != sb.Length) {
if(sb.ToString().Substring(indexEndOfLine + 2, 2) == "||") tableBegin = false;
else {
tableBegin = true;
s += "\r\n|}";
}
}
else {
tableBegin = true;
s += "\r\n|}";
}
sb.Remove(match.Index, indexEndOfLine - match.Index);
sb.Insert(match.Index, s);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = table.Match(sb.ToString(), match.Index + s.Length);
}
}
match = italic.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = italic.Match(sb.ToString(), end);
else {
if(IsNoFlexed(match.Index, noFlexBegin, noFlexEnd, out end))
match = italic.Match(sb.ToString(), end);
else {
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, "''" + match.Value.Substring(1, match.Length - 2) + @"''");
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
match = italic.Match(sb.ToString());
}
}
}
bool first = true;
match = newlinespace.Match(sb.ToString());
while(match.Success) {
int end;
if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end))
match = newlinespace.Match(sb.ToString(), end);
else {
string s = "";
if(first)
s += "{{{{" + match.Value.Substring(1, match.Value.Length - 1);
else
s += match.Value.Substring(1, match.Value.Length - 1);
if(sb.Length > match.Index + match.Length + 1) {
if(sb[match.Index + match.Length] == '\n' && sb[match.Index + match.Length + 1] == ' ') {
first = false;
}
else {
s += "}}}}";
first = true;
}
}
sb.Remove(match.Index, match.Length);
sb.Insert(match.Index, s);
match = newlinespace.Match(sb.ToString(), match.Index + s.Length);
ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd);
}
}
return sb.ToString().Replace("<noflex>", "").Replace("</noflex>", "");
}
private void ComputeNoWiki(string text, ref List<int> noWikiBegin, ref List<int> noWikiEnd) {
Match match;
noWikiBegin.Clear();
noWikiEnd.Clear();
match = noWiki.Match(text);
while(match.Success) {
noWikiBegin.Add(match.Index);
noWikiEnd.Add(match.Index + match.Length - 1);
match = noWiki.Match(text, match.Index + match.Length);
}
}
private bool IsNoWikied(int index, List<int> noWikiBegin, List<int> noWikiEnd, out int end) {
for(int i = 0; i < noWikiBegin.Count; i++) {
if(index >= noWikiBegin[i] && index <= noWikiEnd[i]) {
end = noWikiEnd[i];
return true;
}
}
end = 0;
return false;
}
private void ComputeNoFlex(string text, ref List<int> noFlexBegin, ref List<int> noFlexEnd) {
Match match;
noFlexBegin.Clear();
noFlexEnd.Clear();
match = noFlex.Match(text);
while(match.Success) {
noFlexBegin.Add(match.Index);
noFlexEnd.Add(match.Index + match.Length - 1);
match = noFlex.Match(text, match.Index + match.Length);
}
}
private bool IsNoFlexed(int index, List<int> noFlexBegin, List<int> noFlexEnd, out int end) {
for(int i = 0; i < noFlexBegin.Count; i++) {
if(index >= noFlexBegin[i] && index <= noFlexEnd[i]) {
end = noFlexEnd[i];
return true;
}
}
end = 0;
return false;
}
}
}

145
Core/UrlTools.cs Normal file
View file

@ -0,0 +1,145 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Text;
namespace ScrewTurn.Wiki {
/// <summary>
/// Implements useful URL-handling tools.
/// </summary>
public static class UrlTools {
/// <summary>
/// Properly routes the current virtual request to a physical ASP.NET page.
/// </summary>
public static void RouteCurrentRequest() {
// Extract the physical page name, e.g. MainPage, Edit or Category
string pageName = Path.GetFileNameWithoutExtension(HttpContext.Current.Request.PhysicalPath);
// Exctract the extension, e.g. .ashx or .aspx
string ext = Path.GetExtension(HttpContext.Current.Request.PhysicalPath).ToLowerInvariant();
// Remove trailing dot, .ashx -> ashx
if(ext.Length > 0) ext = ext.Substring(1);
// IIS7+Integrated Pipeline handles all requests through the ASP.NET engine
// All non-interesting files are not processed, such as GIF, CSS, etc.
if(ext != "ashx" && ext != "aspx") return;
// Extract the current namespace, if any
string nspace = GetCurrentNamespace();
if(!string.IsNullOrEmpty(nspace)) pageName = pageName.Substring(nspace.Length + 1); // Trim Namespace. from pageName
//HttpContext.Current.Response.Filter =
// new ScrewTurn.Wiki.RelativeUrlFilterStream(HttpContext.Current.Response.Filter, nspace);
string queryString = ""; // Empty or begins with ampersand, not question mark
try {
// This might throw exceptions if 3rd-party modules interfer with the request pipeline
queryString = HttpContext.Current.Request.Url.Query.Replace("?", "&");
}
catch { }
if(ext.Equals("ashx")) {
// Content page requested, process it via Default.aspx
if(!queryString.Contains("NS=")) {
HttpContext.Current.RewritePath("~/Default.aspx?Page=" + Tools.UrlEncode(pageName) + "&NS=" + Tools.UrlEncode(nspace) + queryString);
}
else {
HttpContext.Current.RewritePath("~/Default.aspx?Page=" + Tools.UrlEncode(pageName) + queryString);
}
}
else if(ext.Equals("aspx")) {
// System page requested, redirect to the root of the application
// For example: http://www.server.com/Namespace.Edit.aspx?Page=MainPage -> http://www.server.com/Edit.aspx?Page=MainPage&NS=Namespace
if(!string.IsNullOrEmpty(nspace)) { // Needed to avoid infinite loops
if(!queryString.Contains("NS=")) {
HttpContext.Current.RewritePath("~/" + Tools.UrlEncode(pageName) + "." + ext + "?NS=" + Tools.UrlEncode(nspace) + queryString);
}
else {
if(queryString.Length > 1) queryString = "?" + queryString.Substring(1);
HttpContext.Current.RewritePath("~/" + Tools.UrlEncode(pageName) + "." + ext + queryString);
}
}
}
// else nothing to do
}
/// <summary>
/// Extracts the current namespace from the URL, such as <i>/App/Namespace.Edit.aspx</i>.
/// </summary>
/// <returns>The current namespace, or an empty string. <c>null</c> if the URL format is not specified.</returns>
public static string GetCurrentNamespace() {
string filename = Path.GetFileNameWithoutExtension(HttpContext.Current.Request.Path); // e.g. MainPage or Edit or Namespace.MainPage or Namespace.Edit
// Use dot to split the filename
string[] fields = filename.Split('.');
if(fields.Length != 1 && fields.Length != 2) return null; // Unrecognized format
if(fields.Length == 1) return ""; // Just page name
else return fields[0]; // Namespace.Page
}
/// <summary>
/// Redirects the current response to the specified URL, properly appending the current namespace if any.
/// </summary>
/// <param name="target">The target URL.</param>
public static void Redirect(string target) {
string nspace = HttpContext.Current.Request["NS"];
if(nspace == null || nspace.Length == 0) HttpContext.Current.Response.Redirect(target);
else HttpContext.Current.Response.Redirect(target + (target.Contains("?") ? "&" : "?") + "NS=" + Tools.UrlEncode(nspace));
}
/// <summary>
/// Builds a URL properly prepending the namespace to the URL.
/// </summary>
/// <param name="chunks">The chunks used to build the URL.</param>
/// <returns>The complete URL.</returns>
public static string BuildUrl(params string[] chunks) {
if(chunks == null) throw new ArgumentNullException("chunks");
if(chunks.Length == 0) return ""; // Shortcut
StringBuilder temp = new StringBuilder(chunks.Length * 10);
foreach(string chunk in chunks) {
temp.Append(chunk);
}
string tempString = temp.ToString();
if(tempString.StartsWith("++")) return tempString.Substring(2);
string nspace = HttpContext.Current.Request["NS"];
if(string.IsNullOrEmpty(nspace)) nspace = null;
if(nspace == null) nspace = GetCurrentNamespace();
if(string.IsNullOrEmpty(nspace)) nspace = null;
if(nspace != null) {
string tempStringLower = tempString.ToLowerInvariant();
if((tempStringLower.Contains(".ashx") || tempStringLower.Contains(".aspx")) && !tempString.StartsWith(Tools.UrlEncode(nspace) + ".")) temp.Insert(0, nspace + ".");
}
return temp.ToString();
}
/// <summary>
/// Builds a URL properly appendind the <b>NS</b> parameter if appropriate.
/// </summary>
/// <param name="destination">The destination <see cref="T:StringBuilder"/>.</param>
/// <param name="chunks">The chunks to append.</param>
public static void BuildUrl(StringBuilder destination, params string[] chunks) {
if(destination == null) throw new ArgumentNullException("destination");
destination.Append(BuildUrl(chunks));
}
/// <summary>
/// Redirects to the default page of the current namespace.
/// </summary>
public static void RedirectHome() {
Redirect(BuildUrl(Settings.DefaultPage, Settings.PageExtension));
}
}
}

921
Core/Users.cs Normal file
View file

@ -0,0 +1,921 @@
using System;
using System.Collections.Generic;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
using System.Web;
namespace ScrewTurn.Wiki {
/// <summary>
/// Manages all the User Accounts data.
/// </summary>
public static class Users {
private static UserInfo adminAccount = null;
/// <summary>
/// Gets the built-in administrator account.
/// </summary>
/// <returns>The account.</returns>
public static UserInfo GetAdministratorAccount() {
if(adminAccount == null) {
adminAccount = new UserInfo("admin", "Administrator", Settings.ContactEmail, true, DateTime.MinValue, null);
adminAccount.Groups = new string[] { Settings.AdministratorsGroup };
}
return adminAccount;
}
/// <summary>
/// The user data key pointing to page changes notification entries.
/// </summary>
private const string PageChangesKey = "PageChanges";
/// <summary>
/// The user data key pointing to discussion messages notification entries.
/// </summary>
private const string DiscussionMessagesKey = "DiscussionMessages";
/// <summary>
/// The user data key pointing to page changes notification entries for whole namespace.
/// </summary>
private const string NamespacePageChangesKey = "NamespacePageChanges";
/// <summary>
/// The user data key pointing to discussion messages notification entries for whole namespaces.
/// </summary>
private const string NamespaceDiscussionMessagesKey = "NamespaceDiscussionMessages";
/// <summary>
/// Gets all the Users that the providers declare to manage.
/// </summary>
/// <returns>The users, sorted by username.</returns>
public static List<UserInfo> GetUsers() {
List<UserInfo> allUsers = new List<UserInfo>(1000);
// Retrieve all the users from the Users Providers
int count = 0;
foreach(IUsersStorageProviderV30 provider in Collectors.UsersProviderCollector.AllProviders) {
count++;
allUsers.AddRange(provider.GetUsers());
}
if(count > 1) {
allUsers.Sort(new UsernameComparer());
}
return allUsers;
}
/// <summary>
/// Finds a user.
/// </summary>
/// <param name="username">The username.</param>
/// <returns>The user, or <c>null</c>.</returns>
public static UserInfo FindUser(string username) {
if(username == "admin") return GetAdministratorAccount();
// Try default provider first
IUsersStorageProviderV30 defaultProvider = Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider);
UserInfo temp = defaultProvider.GetUser(username);
if(temp != null) return temp;
// The try other providers
temp = null;
IUsersStorageProviderV30[] providers = Collectors.UsersProviderCollector.AllProviders;
foreach(IUsersStorageProviderV30 p in providers) {
IUsersStorageProviderV30 extProv = p as IUsersStorageProviderV30;
if(extProv != null && extProv != defaultProvider) {
temp = extProv.GetUser(username);
if(temp != null) return temp;
}
}
return null;
}
/// <summary>
/// Finds a user by email.
/// </summary>
/// <param name="email">The email address.</param>
/// <returns>The user, or <c>null</c>.</returns>
public static UserInfo FindUserByEmail(string email) {
// Try default provider first
IUsersStorageProviderV30 defaultProvider = Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider);
UserInfo temp = defaultProvider.GetUserByEmail(email);
if(temp != null) return temp;
// The try other providers
temp = null;
IUsersStorageProviderV30[] providers = Collectors.UsersProviderCollector.AllProviders;
foreach(IUsersStorageProviderV30 p in providers) {
IUsersStorageProviderV30 extProv = p as IUsersStorageProviderV30;
if(extProv != null && extProv != defaultProvider) {
temp = extProv.GetUserByEmail(email);
if(temp != null) return temp;
}
}
return null;
}
/// <summary>
/// Gets user data.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="key">The data key.</param>
/// <returns>The data, or <c>null</c> if either the user or the key is not found.</returns>
public static string GetUserData(UserInfo user, string key) {
if(user == null) return null;
if(string.IsNullOrEmpty(key)) return null;
if(user.Username == "admin") return null;
return user.Provider.RetrieveUserData(user, key);
}
/// <summary>
/// Sets user data.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="key">The data key.</param>
/// <param name="data">The data value.</param>
/// <returns><c>true</c> if the data is stored, <c>false</c> otherwise.</returns>
public static bool SetUserData(UserInfo user, string key, string data) {
if(user == null) return false;
if(string.IsNullOrEmpty(key)) return false;
if(user.Username == "admin") return false;
if(user.Provider.UsersDataReadOnly) return false;
bool done = user.Provider.StoreUserData(user, key, data);
if(done) Log.LogEntry("User data stored for " + user.Username, EntryType.General, Log.SystemUsername);
else Log.LogEntry("Could not store user data for " + user.Username, EntryType.Error, Log.SystemUsername);
return done;
}
/// <summary>
/// Adds a new User.
/// </summary>
/// <param name="username">The Username.</param>
/// <param name="displayName">The display name.</param>
/// <param name="password">The Password (plain text).</param>
/// <param name="email">The Email address.</param>
/// <param name="active">A value specifying whether or not the account is active.</param>
/// <param name="provider">The Provider. If null, the default provider is used.</param>
/// <returns>True if the User has been created successfully.</returns>
public static bool AddUser(string username, string displayName, string password, string email, bool active, IUsersStorageProviderV30 provider) {
if(FindUser(username) != null) return false;
if(provider == null) provider = Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider);
if(provider.UserAccountsReadOnly) return false;
UserInfo u = provider.AddUser(username, displayName, password, email, active, DateTime.Now);
if(u == null) {
Log.LogEntry("User creation failed for " + username, EntryType.Error, Log.SystemUsername);
return false;
}
else {
Log.LogEntry("User " + username + " created", EntryType.General, Log.SystemUsername);
Host.Instance.OnUserAccountActivity(u, UserAccountActivity.AccountAdded);
return true;
}
}
/// <summary>
/// Updates a new User.
/// </summary>
/// <param name="user">The user to modify.</param>
/// <param name="displayName">The display name.</param>
/// <param name="password">The Password (plain text, <c>null</c> or empty for no change).</param>
/// <param name="email">The Email address.</param>
/// <param name="active">A value specifying whether or not the account is active.</param>
/// <returns>True if the User has been created successfully.</returns>
public static bool ModifyUser(UserInfo user, string displayName, string password, string email, bool active) {
if(user.Provider.UserAccountsReadOnly) return false;
bool done = user.Provider.ModifyUser(user, displayName, password, email, active) != null;
if(done) {
Log.LogEntry("User " + user.Username + " updated", EntryType.General, Log.SystemUsername);
Host.Instance.OnUserAccountActivity(user, UserAccountActivity.AccountModified);
return true;
}
else {
Log.LogEntry("User update failed for " + user.Username, EntryType.Error, Log.SystemUsername);
return false;
}
}
/// <summary>
/// Removes a User.
/// </summary>
/// <param name="user">The User to remove.</param>
/// <returns>True if the User has been removed successfully.</returns>
public static bool RemoveUser(UserInfo user) {
if(user.Provider.UserAccountsReadOnly) return false;
bool done = user.Provider.RemoveUser(user);
if(done) {
Log.LogEntry("User " + user.Username + " removed", EntryType.General, Log.SystemUsername);
Host.Instance.OnUserAccountActivity(user, UserAccountActivity.AccountRemoved);
return true;
}
else {
Log.LogEntry("User deletion failed for " + user.Username, EntryType.Error, Log.SystemUsername);
return false;
}
}
/// <summary>
/// Changes the Password of a User.
/// </summary>
/// <param name="user">The User to change the password of.</param>
/// <param name="newPassword">The new Password (plain text).</param>
/// <returns><c>true</c> if the Password has been changed successfully, <c>false</c> otherwise.</returns>
public static bool ChangePassword(UserInfo user, string newPassword) {
return ModifyUser(user, user.DisplayName, newPassword, user.Email, user.Active);
}
/// <summary>
/// Sends the password reset message to a user.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="email">The email.</param>
/// <param name="dateTime">The user registration date/time.</param>
public static void SendPasswordResetMessage(string username, string email, DateTime dateTime) {
string mainLink = Settings.MainUrl + "Login.aspx?ResetCode=" + Tools.ComputeSecurityHash(username, email, dateTime) + "&Username=" + Tools.UrlEncode(username);
string body = Settings.Provider.GetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null).Replace("##USERNAME##",
username).Replace("##LINK##", mainLink).Replace("##WIKITITLE##",
Settings.WikiTitle).Replace("##EMAILADDRESS##", Settings.ContactEmail);
EmailTools.AsyncSendEmail(email, Settings.SenderEmail,
Settings.WikiTitle + " - " + Exchanger.ResourceExchanger.GetResource("ResetPassword"), body, false);
}
/// <summary>
/// Changes the Email address of a User.
/// </summary>
/// <param name="user">The User to change the Email address of.</param>
/// <param name="newEmail">The new Email address.</param>
/// <returns><c>true</c> if the Email address has been changed successfully, <c>false</c> otherwise.</returns>
public static bool ChangeEmail(UserInfo user, string newEmail) {
return ModifyUser(user, user.DisplayName, null, newEmail, user.Active);
}
/// <summary>
/// Sets the Active/Inactive status of a User.
/// </summary>
/// <param name="user">The User.</param>
/// <param name="active">The status.</param>
/// <returns>True if the User's status has been changed successfully.</returns>
public static bool SetActivationStatus(UserInfo user, bool active) {
return ModifyUser(user, user.DisplayName, null, user.Email, active);
}
/// <summary>
/// Gets all the user groups.
/// </summary>
/// <returns>All the user groups, sorted by name.</returns>
public static List<UserGroup> GetUserGroups() {
List<UserGroup> result = new List<UserGroup>(50);
int count = 0;
foreach(IUsersStorageProviderV30 prov in Collectors.UsersProviderCollector.AllProviders) {
count++;
result.AddRange(prov.GetUserGroups());
}
if(count > 1) {
result.Sort(new UserGroupComparer());
}
return result;
}
/// <summary>
/// Gets all the user groups in a provider.
/// </summary>
/// <param name="provider">The provider.</param>
/// <returns>The user groups, sorted by name.</returns>
public static List<UserGroup> GetUserGroups(IUsersStorageProviderV30 provider) {
return new List<UserGroup>(provider.GetUserGroups());
}
/// <summary>
/// Gets all the user groups a user is member of.
/// </summary>
/// <param name="user">The user.</param>
/// <returns>All the user groups the user is member of, sorted by name.</returns>
public static List<UserGroup> GetUserGroupsForUser(UserInfo user) {
UserGroup[] allGroups = user.Provider.GetUserGroups();
List<UserGroup> result = new List<UserGroup>(allGroups.Length);
StringComparer comp = StringComparer.OrdinalIgnoreCase;
foreach(UserGroup group in allGroups) {
if(Array.Find(user.Groups, delegate(string g) {
return comp.Compare(g, group.Name) == 0;
}) != null) {
result.Add(group);
}
}
return result;
}
/// <summary>
/// Finds a user group.
/// </summary>
/// <param name="name">The name of the user group to find.</param>
/// <returns>The <see cref="T:UserGroup" /> object or <c>null</c> if no data is found.</returns>
public static UserGroup FindUserGroup(string name) {
List<UserGroup> allGroups = GetUserGroups();
int index = allGroups.BinarySearch(new UserGroup(name, "", null), new UserGroupComparer());
if(index < 0) return null;
else return allGroups[index];
}
/// <summary>
/// Adds a new user group to a specific provider.
/// </summary>
/// <param name="name">The name of the group.</param>
/// <param name="description">The description of the group.</param>
/// <param name="provider">The target provider.</param>
/// <returns><c>true</c> if the groups is added, <c>false</c> otherwise.</returns>
public static bool AddUserGroup(string name, string description, IUsersStorageProviderV30 provider) {
if(provider == null) provider = Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider);
if(provider.UserGroupsReadOnly) return false;
if(FindUserGroup(name) != null) return false;
UserGroup result = provider.AddUserGroup(name, description);
if(result != null) {
Host.Instance.OnUserGroupActivity(result, UserGroupActivity.GroupAdded);
Log.LogEntry("User Group " + name + " created", EntryType.General, Log.SystemUsername);
}
else Log.LogEntry("Creation failed for User Group " + name, EntryType.Error, Log.SystemUsername);
return result != null;
}
/// <summary>
/// Adds a new user group to the default provider.
/// </summary>
/// <param name="name">The name of the group.</param>
/// <param name="description">The description of the group.</param>
/// <returns><c>true</c> if the groups is added, <c>false</c> otherwise.</returns>
public static bool AddUserGroup(string name, string description) {
return AddUserGroup(name, description,
Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider));
}
/// <summary>
/// Modifies a user group.
/// </summary>
/// <param name="group">The user group to modify.</param>
/// <param name="description">The new description.</param>
/// <returns><c>true</c> if the user group is modified, <c>false</c> otherwise.</returns>
public static bool ModifyUserGroup(UserGroup group, string description) {
if(group.Provider.UserGroupsReadOnly) return false;
UserGroup result = group.Provider.ModifyUserGroup(group, description);
if(result != null) {
Host.Instance.OnUserGroupActivity(result, UserGroupActivity.GroupModified);
Log.LogEntry("User Group " + group.Name + " updated", EntryType.General, Log.SystemUsername);
}
else Log.LogEntry("Update failed for User Group " + result.Name, EntryType.Error, Log.SystemUsername);
return result != null;
}
/// <summary>
/// Removes a user group.
/// </summary>
/// <param name="group">The user group to remove.</param>
/// <returns><c>true</c> if the user group is removed, <c>false</c> otherwise.</returns>
public static bool RemoveUserGroup(UserGroup group) {
if(group.Provider.UserGroupsReadOnly) return false;
bool done = group.Provider.RemoveUserGroup(group);
if(done) {
Host.Instance.OnUserGroupActivity(group, UserGroupActivity.GroupRemoved);
Log.LogEntry("User Group " + group.Name + " deleted", EntryType.General, Log.SystemUsername);
}
else Log.LogEntry("Deletion failed for User Group " + group.Name, EntryType.Error, Log.SystemUsername);
return done;
}
/// <summary>
/// Sets the group memberships of a user account.
/// </summary>
/// <param name="user">The user account.</param>
/// <param name="groups">The groups the user account is member of.</param>
/// <returns><c>true</c> if the membership is set, <c>false</c> otherwise.</returns>
public static bool SetUserMembership(UserInfo user, string[] groups) {
if(user.Provider.GroupMembershipReadOnly) return false;
UserInfo result = user.Provider.SetUserMembership(user, groups);
if(result != null) {
Host.Instance.OnUserAccountActivity(result, UserAccountActivity.AccountMembershipChanged);
Log.LogEntry("Group membership set for User " + user.Username, EntryType.General, Log.SystemUsername);
}
else Log.LogEntry("Could not set group membership for User " + user.Username, EntryType.Error, Log.SystemUsername);
return result != null;
}
/// <summary>
/// Creates the correct link of a User.
/// </summary>
/// <param name="username">The Username.</param>
/// <returns>The User link.</returns>
public static string UserLink(string username) {
return UserLink(username, false);
}
/// <summary>
/// Creates the correct link of a User.
/// </summary>
/// <param name="username">The Username.</param>
/// <param name="newWindow">A value indicating whether to open the link in a new window.</param>
/// <returns>The User link.</returns>
public static string UserLink(string username, bool newWindow) {
if(username != null && (username.EndsWith("+" + Log.SystemUsername) || username == Log.SystemUsername)) return username;
UserInfo u = FindUser(username);
if(u == null && username.Equals("admin")) u = new UserInfo("admin", null, Settings.ContactEmail, true, DateTime.Now, null);
if(u != null) {
return @"<a " +
(newWindow ? "target=\"_blank\" " : "") +
@"href=""" + UrlTools.BuildUrl("User.aspx?Username=", Tools.UrlEncode(u.Username)) + @""">" +
GetDisplayName(u) + "</a>";
}
else return username;
}
/// <summary>
/// Gets the display name of a user.
/// </summary>
/// <param name="user">The user.</param>
/// <returns>The display name.</returns>
public static string GetDisplayName(UserInfo user) {
if(string.IsNullOrEmpty(user.DisplayName)) return user.Username;
else return user.DisplayName;
}
/// <summary>
/// Tries to automatically login a user using the current HttpContext,
/// through any provider that supports the operation.
/// </summary>
/// <param name="context">The current HttpContext.</param>
/// <returns>The correct UserInfo, or <c>null</c>.</returns>
public static UserInfo TryAutoLogin(HttpContext context) {
// Try default provider first
IUsersStorageProviderV30 defaultProvider =
Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider) as IUsersStorageProviderV30;
if(defaultProvider != null) {
UserInfo temp = defaultProvider.TryAutoLogin(context);
if(temp != null) return temp;
}
// Then try all other providers
IUsersStorageProviderV30[] providers = Collectors.UsersProviderCollector.AllProviders;
foreach(IUsersStorageProviderV30 p in providers) {
IUsersStorageProviderV30 extProv = p as IUsersStorageProviderV30;
if(extProv != null && extProv != defaultProvider) {
UserInfo temp = extProv.TryAutoLogin(context);
if(temp != null) return temp;
}
}
return null;
}
/// <summary>
/// Tries to manually login a user using all the available methods.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
/// <returns>The correct UserInfo, or <c>null</c>.</returns>
public static UserInfo TryLogin(string username, string password) {
if(username == "admin" && password == Settings.MasterPassword) {
return GetAdministratorAccount();
}
// Try default provider first
IUsersStorageProviderV30 defaultProvider =
Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider) as IUsersStorageProviderV30;
if(defaultProvider != null) {
UserInfo temp = defaultProvider.TryManualLogin(username, password);
if(temp != null) return temp;
}
// Then try all other providers
IUsersStorageProviderV30[] providers = Collectors.UsersProviderCollector.AllProviders;
foreach(IUsersStorageProviderV30 p in providers) {
IUsersStorageProviderV30 extProv = p as IUsersStorageProviderV30;
if(extProv != null && extProv != defaultProvider) {
UserInfo temp = extProv.TryManualLogin(username, password);
if(temp != null) return temp;
}
}
return null;
}
/// <summary>
/// Tries to login a user through the cookie-stored authentication data.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="loginKey">The login key.</param>
/// <returns>The correct UserInfo object, or <c>null</c>.</returns>
public static UserInfo TryCookieLogin(string username, string loginKey) {
if(username == "admin" && loginKey == ComputeLoginKey(username, Settings.ContactEmail, DateTime.MinValue)) {
// Just return, no notification to providers because the "admin" account is fictitious
return GetAdministratorAccount();
}
UserInfo user = FindUser(username);
if(user != null && user.Active) {
if(loginKey == ComputeLoginKey(user.Username, user.Email, user.DateTime)) {
// Notify provider
user.Provider.NotifyCookieLogin(user);
return user;
}
}
return null;
}
/// <summary>
/// Notifies to the proper provider that a user has logged out.
/// </summary>
/// <param name="username">The username.</param>
public static void NotifyLogout(string username) {
UserInfo user = FindUser(username);
if(user != null) {
IUsersStorageProviderV30 prov = user.Provider as IUsersStorageProviderV30;
if(prov != null) prov.NotifyLogout(user);
}
}
/// <summary>
/// Copmputes the login key.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="email">The email.</param>
/// <param name="dateTime">The registration date/time.</param>
/// <returns>The login key.</returns>
public static string ComputeLoginKey(string username, string email, DateTime dateTime) {
if(username == null) throw new ArgumentNullException("username");
if(email == null) throw new ArgumentNullException("email");
return Tools.ComputeSecurityHash(username, email, dateTime);
}
/// <summary>
/// Sets the email notification status for a page.
/// </summary>
/// <param name="user">The user for which to set the notification status.</param>
/// <param name="page">The page subject of the notification.</param>
/// <param name="pageChanges">A value indicating whether page changes should be notified.</param>
/// <param name="discussionMessages">A value indicating whether discussion messages should be notified.</param>
/// <returns><c>true</c> if the notification is set, <c>false</c> otherwise.</returns>
public static bool SetEmailNotification(UserInfo user, PageInfo page, bool pageChanges, bool discussionMessages) {
if(user == null || page == null) return false;
// Get user's data
// Depending on the status of pageChanges and discussionMessages,
// either remove existing entries (if any) or add new entries
// In the process, remove entries that refer to inexistent pages
// Format
// Page1:Page2:Page3
string pageChangeData = user.Provider.RetrieveUserData(user, PageChangesKey);
string discussionMessagesData = user.Provider.RetrieveUserData(user, DiscussionMessagesKey);
if(pageChangeData == null) pageChangeData = "";
if(discussionMessagesData == null) discussionMessagesData = "";
string[] pageChangesEntries = pageChangeData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
string[] discussionMessagesEntries = discussionMessagesData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
List<string> pageChangesResult = new List<string>(pageChangesEntries.Length + 1);
List<string> discussionMessagesResult = new List<string>(discussionMessagesEntries.Length + 1);
string lowercasePage = page.FullName.ToLowerInvariant();
bool added = false;
foreach(string entry in pageChangesEntries) {
if(entry.ToLowerInvariant() == lowercasePage) {
if(pageChanges) {
pageChangesResult.Add(entry);
added = true;
}
}
else if(Pages.FindPage(entry) != null) pageChangesResult.Add(entry);
}
if(!added && pageChanges) pageChangesResult.Add(page.FullName);
added = false;
foreach(string entry in discussionMessagesEntries) {
if(entry.ToLowerInvariant() == lowercasePage) {
if(discussionMessages) {
discussionMessagesResult.Add(entry);
added = true;
}
}
else if(Pages.FindPage(entry) != null) discussionMessagesResult.Add(entry);
}
if(!added && discussionMessages) discussionMessagesResult.Add(page.FullName);
string newPageChangesData = string.Join(":", pageChangesResult.ToArray());
string newDiscussionMessagesData = string.Join(":", discussionMessagesResult.ToArray());
bool done = user.Provider.StoreUserData(user, PageChangesKey, newPageChangesData) &
user.Provider.StoreUserData(user, DiscussionMessagesKey, newDiscussionMessagesData);
return done;
}
/// <summary>
/// Sets the email notification status for a namespace.
/// </summary>
/// <param name="user">The user for which to set the notification status.</param>
/// <param name="nspace">The namespace subject of the notification.</param>
/// <param name="pageChanges">A value indicating whether page changes should be notified.</param>
/// <param name="discussionMessages">A value indicating whether discussion messages should be notified.</param>
/// <returns><c>true</c> if the notification is set, <c>false</c> otherwise.</returns>
public static bool SetEmailNotification(UserInfo user, NamespaceInfo nspace, bool pageChanges, bool discussionMessages) {
if(user == null) return false;
// Get user's data
// Depending on the status of pageChanges and discussionMessages,
// either remove existing entries (if any) or add new entries
// In the process, remove entries that refer to inexistent pages
// Format
// Namespace1:Namespace2:Namespace3
string pageChangeData = user.Provider.RetrieveUserData(user, NamespacePageChangesKey);
string discussionMessagesData = user.Provider.RetrieveUserData(user, NamespaceDiscussionMessagesKey);
if(pageChangeData == null) pageChangeData = "";
if(discussionMessagesData == null) discussionMessagesData = "";
string[] pageChangesEntries = pageChangeData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
string[] discussionMessagesEntries = discussionMessagesData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
List<string> pageChangesResult = new List<string>(pageChangesEntries.Length + 1);
List<string> discussionMessagesResult = new List<string>(discussionMessagesEntries.Length + 1);
string namespaceName = nspace != null ? nspace.Name : "<root>";
string lowercaseNamespace = nspace != null ? nspace.Name.ToLowerInvariant() : "<root>";
bool added = false;
foreach(string entry in pageChangesEntries) {
if(entry.ToLowerInvariant() == lowercaseNamespace) {
if(pageChanges) {
pageChangesResult.Add(entry);
added = true;
}
}
else {
if(entry == "<root>") pageChangesResult.Add("<root>");
else if(Pages.FindNamespace(entry) != null) pageChangesResult.Add(entry);
}
}
if(!added && pageChanges) pageChangesResult.Add(namespaceName);
added = false;
foreach(string entry in discussionMessagesEntries) {
if(entry.ToLowerInvariant() == lowercaseNamespace) {
if(discussionMessages) {
discussionMessagesResult.Add(entry);
added = true;
}
}
else {
if(entry == "<root>") discussionMessagesResult.Add("<root>");
else if(Pages.FindNamespace(entry) != null) discussionMessagesResult.Add(entry);
}
}
if(!added && discussionMessages) discussionMessagesResult.Add(namespaceName);
string newPageChangesData = string.Join(":", pageChangesResult.ToArray());
string newDiscussionMessagesData = string.Join(":", discussionMessagesResult.ToArray());
bool done = user.Provider.StoreUserData(user, NamespacePageChangesKey, newPageChangesData) &
user.Provider.StoreUserData(user, NamespaceDiscussionMessagesKey, newDiscussionMessagesData);
return done;
}
/// <summary>
/// Gets the email notification status for a page.
/// </summary>
/// <param name="user">The user for which to get the notification status.</param>
/// <param name="page">The page subject of the notification.</param>
/// <param name="pageChanges">A value indicating whether page changes should be notified.</param>
/// <param name="discussionMessages">A value indicating whether discussion messages should be notified.</param>
public static void GetEmailNotification(UserInfo user, PageInfo page, out bool pageChanges, out bool discussionMessages) {
pageChanges = false;
discussionMessages = false;
if(user == null || page == null) return;
string pageChangeData = user.Provider.RetrieveUserData(user, PageChangesKey);
string discussionMessagesData = user.Provider.RetrieveUserData(user, DiscussionMessagesKey);
if(pageChangeData == null) pageChangeData = "";
if(discussionMessagesData == null) discussionMessagesData = "";
pageChangeData = pageChangeData.ToLowerInvariant();
discussionMessagesData = discussionMessagesData.ToLowerInvariant();
string[] pageChangeEntries = pageChangeData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
string[] discussionMessagesEntries = discussionMessagesData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
string lowercasePage = page.FullName.ToLowerInvariant();
// Elements in the array are already lowercase
pageChanges = Array.Find(pageChangeEntries, delegate(string elem) { return elem == lowercasePage; }) != null;
discussionMessages = Array.Find(discussionMessagesEntries, delegate(string elem) { return elem == lowercasePage; }) != null;
}
/// <summary>
/// Gets the email notification status for a namespace.
/// </summary>
/// <param name="user">The user for which to get the notification status.</param>
/// <param name="nspace">The namespace subject of the notification (<c>null</c> for the root).</param>
/// <param name="pageChanges">A value indicating whether page changes should be notified.</param>
/// <param name="discussionMessages">A value indicating whether discussion messages should be notified.</param>
public static void GetEmailNotification(UserInfo user, NamespaceInfo nspace, out bool pageChanges, out bool discussionMessages) {
pageChanges = false;
discussionMessages = false;
if(user == null) return;
string lowercaseNamespaces = nspace != null ? nspace.Name.ToLowerInvariant() : "<root>";
string pageChangesData = user.Provider.RetrieveUserData(user, NamespacePageChangesKey);
string discussionMessagesData = user.Provider.RetrieveUserData(user, NamespaceDiscussionMessagesKey);
if(pageChangesData == null) pageChangesData = "";
if(discussionMessagesData == null) discussionMessagesData = "";
pageChangesData = pageChangesData.ToLowerInvariant();
discussionMessagesData = discussionMessagesData.ToLowerInvariant();
string[] pageChangeEntries = pageChangesData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
string[] discussionMessagesEntries = discussionMessagesData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
// Elements in the array are already lowercase
pageChanges = Array.Find(pageChangeEntries, delegate(string elem) { return elem == lowercaseNamespaces; }) != null;
discussionMessages = Array.Find(discussionMessagesEntries, delegate(string elem) { return elem == lowercaseNamespaces; }) != null;
}
/// <summary>
/// Gets all the users that must be notified of a page change.
/// </summary>
/// <param name="page">The page.</param>
/// <returns>The users to be notified.</returns>
public static UserInfo[] GetUsersToNotifyForPageChange(PageInfo page) {
if(page == null) return new UserInfo[0];
UserInfo[] specific = GetUsersToNotify(page, PageChangesKey);
UserInfo[] nspace = GetUsersToNotify(Pages.FindNamespace(NameTools.GetNamespace(page.FullName)),
NamespacePageChangesKey);
UserInfo[] temp = MergeArrays(specific, nspace);
List<UserInfo> result = new List<UserInfo>(temp.Length);
// Verify read permissions
foreach(UserInfo user in temp) {
if(AuthChecker.CheckActionForPage(page, Actions.ForPages.ReadPage, user.Username, user.Groups)) {
result.Add(user);
}
}
return result.ToArray();
}
/// <summary>
/// Gets all the users that must be notified of a discussion message.
/// </summary>
/// <param name="page">The page.</param>
/// <returns>The users to be notified.</returns>
public static UserInfo[] GetUsersToNotifyForDiscussionMessages(PageInfo page) {
if(page == null) return new UserInfo[0];
UserInfo[] specific = GetUsersToNotify(page, DiscussionMessagesKey);
UserInfo[] nspace = GetUsersToNotify(Pages.FindNamespace(NameTools.GetNamespace(page.FullName)),
NamespaceDiscussionMessagesKey);
UserInfo[] temp = MergeArrays(specific, nspace);
List<UserInfo> result = new List<UserInfo>(temp.Length);
// Verify read permissions
foreach(UserInfo user in temp) {
if(AuthChecker.CheckActionForPage(page, Actions.ForPages.ReadDiscussion, user.Username, user.Groups)) {
result.Add(user);
}
}
return result.ToArray();
}
/// <summary>
/// Merges two arrays of users, removing duplicates.
/// </summary>
/// <param name="array1">The first array.</param>
/// <param name="array2">The second array.</param>
/// <returns>The merged users.</returns>
private static UserInfo[] MergeArrays(UserInfo[] array1, UserInfo[] array2) {
List<UserInfo> result = new List<UserInfo>(array1.Length + array2.Length);
result.AddRange(array1);
UsernameComparer comp = new UsernameComparer();
foreach(UserInfo user in array2) {
bool found = false;
foreach(UserInfo present in result) {
if(comp.Compare(present, user) == 0) {
found = true;
break;
}
}
if(!found) {
result.Add(user);
}
}
return result.ToArray();
}
/// <summary>
/// Gets the users to notify for either a page change or a discussion message.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="key">The key to look for in the user's data.</param>
/// <returns>The users to be notified.</returns>
private static UserInfo[] GetUsersToNotify(PageInfo page, string key) {
List<UserInfo> result = new List<UserInfo>(200);
string lowercasePage = page.FullName.ToLowerInvariant();
foreach(IUsersStorageProviderV30 prov in Collectors.UsersProviderCollector.AllProviders) {
IDictionary<UserInfo, string> users = prov.GetUsersWithData(key);
string[] fields;
foreach(KeyValuePair<UserInfo, string> pair in users) {
fields = pair.Value.ToLowerInvariant().Split(':');
if(Array.Find(fields, delegate(string elem) { return elem == lowercasePage; }) != null) {
result.Add(pair.Key);
}
}
}
return result.ToArray();
}
/// <summary>
/// Gets the users to notify for either a page change or a discussion message in a namespace.
/// </summary>
/// <param name="nspace">The namespace (<c>null</c> for the root).</param>
/// <param name="key">The key to look for in the user's data.</param>
/// <returns>The users to be notified.</returns>
private static UserInfo[] GetUsersToNotify(NamespaceInfo nspace, string key) {
List<UserInfo> result = new List<UserInfo>(200);
string lowercaseNamespace = nspace != null ? nspace.Name.ToLowerInvariant() : "<root>";
foreach(IUsersStorageProviderV30 prov in Collectors.UsersProviderCollector.AllProviders) {
IDictionary<UserInfo, string> users = prov.GetUsersWithData(key);
string[] fields;
foreach(KeyValuePair<UserInfo, string> pair in users) {
fields = pair.Value.ToLowerInvariant().Split(':');
if(Array.Find(fields, delegate(string elem) { return elem == lowercaseNamespace; }) != null) {
result.Add(pair.Key);
}
}
}
return result.ToArray();
}
}
}

View file

@ -0,0 +1,970 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ScrewTurn.Wiki.PluginFramework;
namespace ScrewTurn.Wiki {
/// <summary>
/// Implements a Users Storage Provider.
/// </summary>
public class UsersStorageProvider : IUsersStorageProviderV30 {
private const string UsersFile = "Users.cs";
private const string UsersDataFile = "UsersData.cs";
private const string GroupsFile = "Groups.cs";
private readonly ComponentInformation info = new ComponentInformation("Local Users Provider",
"ScrewTurn Software", Settings.WikiVersion, "http://www.screwturn.eu", null);
private IHostV30 host;
private UserGroup[] groupsCache = null;
private UserInfo[] usersCache = null;
private string GetFullPath(string filename) {
return Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), filename);
}
/// <summary>
/// Initializes the Provider.
/// </summary>
/// <param name="host">The Host of the Provider.</param>
/// <param name="config">The Configuration data, if any.</param>
/// <exception cref="ArgumentNullException">If <b>host</b> or <b>config</b> are <c>null</c>.</exception>
/// <exception cref="InvalidConfigurationException">If <b>config</b> is not valid or is incorrect.</exception>
public void Init(IHostV30 host, string config) {
if(host == null) throw new ArgumentNullException("host");
if(config == null) throw new ArgumentNullException("config");
this.host = host;
if(!LocalProvidersTools.CheckWritePermissions(host.GetSettingValue(SettingName.PublicDirectory))) {
throw new InvalidConfigurationException("Cannot write into the public directory - check permissions");
}
if(!File.Exists(GetFullPath(UsersFile))) {
File.Create(GetFullPath(UsersFile)).Close();
}
if(!File.Exists(GetFullPath(UsersDataFile))) {
File.Create(GetFullPath(UsersDataFile)).Close();
}
if(!File.Exists(GetFullPath(GroupsFile))) {
File.Create(GetFullPath(GroupsFile)).Close();
}
VerifyAndPerformUpgrade();
}
/// <summary>
/// Verifies the need for a data upgrade, and performs it when needed.
/// </summary>
private void VerifyAndPerformUpgrade() {
// Load file lines
// Parse first line (if any) with old (v2) algorithm
// If parsing is successful, then the file must be converted
// Conversion consists in removing the 'ADMIN|USER' field, creating the proper default groups and setting user membership
// Structure v2:
// Username|PasswordHash|Email|Active-Inactive|DateTime|Admin-User
//string[] lines = File.ReadAllLines(GetFullPath(UsersFile));
// Use this method because version 2.0 file might have started with a blank line
string[] lines = File.ReadAllText(GetFullPath(UsersFile)).Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
if(lines.Length > 0) {
bool upgradeIsNeeded = false;
LocalUserInfo[] users = new LocalUserInfo[lines.Length];
bool[] oldStyleAdmin = new bool[lines.Length]; // Values are valid only if upgradeIsNeeded=true
char[] splitter = new char[] { '|' };
for(int i = 0; i < lines.Length; i++) {
string line = lines[i];
string[] fields = line.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
string displayName = null;
if(fields.Length == 6) {
if(fields[5] == "ADMIN" || fields[5] == "USER") {
// Version 2.0
upgradeIsNeeded = true;
oldStyleAdmin[i] = fields[5] == "ADMIN";
}
else {
// Version 3.0 with DisplayName specified
oldStyleAdmin[i] = false;
displayName = fields[5];
}
}
else {
// Can be a version 3.0 file, with empty DisplayName
oldStyleAdmin[i] = false;
}
users[i] = new LocalUserInfo(fields[0], displayName, fields[2],
fields[3].ToLowerInvariant() == "active", DateTime.Parse(fields[4]), this, fields[1]);
}
if(upgradeIsNeeded) {
// Dump users
// Create default groups
// Set membership for old users
// Tell the host to set the permissions for the default groups
string backupFile = GetFullPath(Path.GetFileNameWithoutExtension(UsersFile) + "_v2" + Path.GetExtension(UsersFile));
File.Copy(GetFullPath(UsersFile), backupFile);
host.LogEntry("Upgrading users format from 2.0 to 3.0", LogEntryType.General, null, this);
DumpUsers(users);
UserGroup adminsGroup = AddUserGroup(host.GetSettingValue(SettingName.AdministratorsGroup), "Built-in Administrators");
UserGroup usersGroup = AddUserGroup(host.GetSettingValue(SettingName.UsersGroup), "Built-in Users");
for(int i = 0; i < users.Length; i++) {
if(oldStyleAdmin[i]) {
SetUserMembership(users[i], new string[] { adminsGroup.Name });
}
else {
SetUserMembership(users[i], new string[] { usersGroup.Name });
}
}
host.UpgradeSecurityFlagsToGroupsAcl(adminsGroup, usersGroup);
}
}
}
/// <summary>
/// Method invoked on shutdown.
/// </summary>
/// <remarks>This method might not be invoked in some cases.</remarks>
public void Shutdown() { }
/// <summary>
/// Gets the Information about the Provider.
/// </summary>
public ComponentInformation Information {
get { return info; }
}
/// <summary>
/// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
/// </summary>
public string ConfigHelpHtml {
get { return null; }
}
/// <summary>
/// Gets a value indicating whether user accounts are read-only.
/// </summary>
public bool UserAccountsReadOnly {
get { return false; }
}
/// <summary>
/// Gets a value indicating whether user groups are read-only. If so, the provider
/// should support default user groups as defined in the wiki configuration.
/// </summary>
public bool UserGroupsReadOnly {
get { return false; }
}
/// <summary>
/// Gets a value indicating whether group membership is read-only (if <see cref="UserAccountsReadOnly" />
/// is <c>false</c>, then this property must be <c>false</c>). If this property is <c>true</c>, the provider
/// should return membership data compatible with default user groups.
/// </summary>
public bool GroupMembershipReadOnly {
get { return false; }
}
/// <summary>
/// Gets a value indicating whether users' data is read-only.
/// </summary>
public bool UsersDataReadOnly {
get { return false; }
}
/// <summary>
/// Tests a Password for a User account.
/// </summary>
/// <param name="user">The User account.</param>
/// <param name="password">The Password to test.</param>
/// <returns>True if the Password is correct.</returns>
/// <exception cref="ArgumentNullException">If <b>user</b> or <b>password</b> are <c>null</c>.</exception>
public bool TestAccount(UserInfo user, string password) {
if(user == null) throw new ArgumentNullException("user");
if(password == null) throw new ArgumentNullException("password");
return TryManualLogin(user.Username, password) != null;
}
/// <summary>
/// Gets the complete list of Users.
/// </summary>
/// <returns>All the Users, sorted by username.</returns>
public UserInfo[] GetUsers() {
lock(this) {
if(usersCache == null) {
UserGroup[] groups = GetUserGroups();
string[] lines = File.ReadAllLines(GetFullPath(UsersFile));
UserInfo[] result = new UserInfo[lines.Length];
char[] splitter = new char[] { '|' };
string[] fields;
for(int i = 0; i < lines.Length; i++) {
fields = lines[i].Split(splitter, StringSplitOptions.RemoveEmptyEntries);
// Structure (version 3.0 - file previously converted):
// Username|PasswordHash|Email|Active-Inactive|DateTime[|DisplayName]
string displayName = fields.Length == 6 ? fields[5] : null;
result[i] = new LocalUserInfo(fields[0], displayName, fields[2], fields[3].ToLowerInvariant().Equals("active"),
DateTime.Parse(fields[4]), this, fields[1]);
result[i].Groups = GetGroupsForUser(result[i].Username, groups);
}
Array.Sort(result, new UsernameComparer());
usersCache = result;
}
return usersCache;
}
}
/// <summary>
/// Gets the names of all the groups a user is member of.
/// </summary>
/// <param name="user">The username.</param>
/// <param name="groups">The groups.</param>
/// <returns>The names of the groups the user is member of.</returns>
private string[] GetGroupsForUser(string user, UserGroup[] groups) {
List<string> result = new List<string>(3);
foreach(UserGroup group in groups) {
if(Array.Find(group.Users, delegate(string u) { return u == user; }) != null) {
result.Add(group.Name);
}
}
return result.ToArray();
}
/// <summary>
/// Loads a proper local instance of a user account.
/// </summary>
/// <param name="user">The user account.</param>
/// <returns>The local instance, or <c>null</c>.</returns>
private LocalUserInfo LoadLocalInstance(UserInfo user) {
UserInfo[] users = GetUsers();
UsernameComparer comp = new UsernameComparer();
for(int i = 0; i < users.Length; i++) {
if(comp.Compare(users[i], user) == 0) return users[i] as LocalUserInfo;
}
return null;
}
/// <summary>
/// Searches for a User.
/// </summary>
/// <param name="user">The User to search for.</param>
/// <returns>True if the User already exists.</returns>
private bool UserExists(UserInfo user) {
UserInfo[] users = GetUsers();
UsernameComparer comp = new UsernameComparer();
for(int i = 0; i < users.Length; i++) {
if(comp.Compare(users[i], user) == 0) return true;
}
return false;
}
/// <summary>
/// Adds a new User.
/// </summary>
/// <param name="username">The Username.</param>
/// <param name="displayName">The display name (can be <c>null</c>).</param>
/// <param name="password">The Password.</param>
/// <param name="email">The Email address.</param>
/// <param name="active">A value specifying whether or not the account is active.</param>
/// <param name="dateTime">The Account creation Date/Time.</param>
/// <returns>The correct <see cref="T:UserInfo"/> object or <c>null</c>.</returns>
/// <exception cref="ArgumentNullException">If <b>username</b>, <b>password</b> or <b>email</b> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>username</b>, <b>password</b> or <b>email</b> are empty.</exception>
public UserInfo AddUser(string username, string displayName, string password, string email, bool active, DateTime dateTime) {
if(username == null) throw new ArgumentNullException("username");
if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username");
if(password == null) throw new ArgumentNullException("password");
if(password.Length == 0) throw new ArgumentException("Password cannot be empty", "password");
if(email == null) throw new ArgumentNullException("email");
if(email.Length == 0) throw new ArgumentException("Email cannot be empty", "email");
lock(this) {
if(UserExists(new UserInfo(username, displayName, "", true, DateTime.Now, this))) return null;
BackupUsersFile();
StringBuilder sb = new StringBuilder();
sb.Append(username);
sb.Append("|");
sb.Append(Hash.Compute(password));
sb.Append("|");
sb.Append(email);
sb.Append("|");
sb.Append(active ? "ACTIVE" : "INACTIVE");
sb.Append("|");
sb.Append(dateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss"));
// ADMIN|USER no more used in version 3.0
//sb.Append("|");
//sb.Append(admin ? "ADMIN" : "USER");
if(!string.IsNullOrEmpty(displayName)) {
sb.Append("|");
sb.Append(displayName);
}
sb.Append("\r\n");
File.AppendAllText(GetFullPath(UsersFile), sb.ToString());
usersCache = null;
return new LocalUserInfo(username, displayName, email, active, dateTime, this, Hash.Compute(password));
}
}
/// <summary>
/// Modifies a User.
/// </summary>
/// <param name="user">The Username of the user to modify.</param>
/// <param name="newDisplayName">The new display name (can be <c>null</c>).</param>
/// <param name="newPassword">The new Password (<c>null</c> or blank to keep the current password).</param>
/// <param name="newEmail">The new Email address.</param>
/// <param name="newActive">A value indicating whether the account is active.</param>
/// <returns>The correct <see cref="T:UserInfo"/> object or <c>null</c>.</returns>
/// <exception cref="ArgumentNullException">If <b>user</b> or <b>newEmail</b> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>newEmail</b> is empty.</exception>
public UserInfo ModifyUser(UserInfo user, string newDisplayName, string newPassword, string newEmail, bool newActive) {
if(user == null) throw new ArgumentNullException("user");
if(newEmail == null) throw new ArgumentNullException("newEmail");
if(newEmail.Length == 0) throw new ArgumentException("New Email cannot be empty", "newEmail");
lock(this) {
LocalUserInfo local = LoadLocalInstance(user);
if(local == null) return null;
UserInfo[] allUsers = GetUsers();
UsernameComparer comp = new UsernameComparer();
usersCache = null;
for(int i = 0; i < allUsers.Length; i++) {
if(comp.Compare(allUsers[i], user) == 0) {
LocalUserInfo result = new LocalUserInfo(user.Username, newDisplayName, newEmail,
newActive, user.DateTime, this,
string.IsNullOrEmpty(newPassword) ? local.PasswordHash : Hash.Compute(newPassword));
result.Groups = allUsers[i].Groups;
allUsers[i] = result;
DumpUsers(allUsers);
return result;
}
}
}
return null;
}
/// <summary>
/// Removes a User.
/// </summary>
/// <param name="user">The User to remove.</param>
/// <returns>True if the User has been removed successfully.</returns>
/// <exception cref="ArgumentNullException">If <b>user</b> is <c>null</c>.</exception>
public bool RemoveUser(UserInfo user) {
if(user == null) throw new ArgumentNullException("user");
lock(this) {
UserInfo[] users = GetUsers();
UsernameComparer comp = new UsernameComparer();
int idx = -1;
for(int i = 0; i < users.Length; i++) {
if(comp.Compare(users[i], user) == 0) {
idx = i;
break;
}
}
if(idx < 0) return false;
// Remove user's data
string lowercaseUsername = user.Username.ToLowerInvariant();
string[] lines = File.ReadAllLines(GetFullPath(UsersDataFile));
List<string> newLines = new List<string>(lines.Length);
string[] fields;
for(int i = 0; i < lines.Length; i++) {
fields = lines[i].Split('|');
if(fields[0].ToLowerInvariant() != lowercaseUsername) {
newLines.Add(lines[i]);
}
}
File.WriteAllLines(GetFullPath(UsersDataFile), newLines.ToArray());
// Remove user
List<UserInfo> tmp = new List<UserInfo>(users);
tmp.Remove(tmp[idx]);
DumpUsers(tmp.ToArray());
usersCache = null;
}
return true;
}
private void BackupUsersFile() {
lock(this) {
File.Copy(GetFullPath(UsersFile),
GetFullPath(Path.GetFileNameWithoutExtension(UsersFile) +
".bak" + Path.GetExtension(UsersFile)), true);
}
}
/// <summary>
/// Writes on disk all the Users.
/// </summary>
/// <param name="users">The User list.</param>
/// <remarks>This method does not lock resources, therefore a lock is need in the caller.</remarks>
private void DumpUsers(UserInfo[] users) {
lock(this) {
BackupUsersFile();
StringBuilder sb = new StringBuilder();
for(int i = 0; i < users.Length; i++) {
LocalUserInfo u = (LocalUserInfo)users[i];
sb.Append(u.Username);
sb.Append("|");
sb.Append(u.PasswordHash);
sb.Append("|");
sb.Append(u.Email);
sb.Append("|");
sb.Append(u.Active ? "ACTIVE" : "INACTIVE");
sb.Append("|");
sb.Append(u.DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss"));
// ADMIN|USER no more used in version 3.0
//sb.Append("|");
//sb.Append(u.Admin ? "ADMIN" : "USER");
if(!string.IsNullOrEmpty(u.DisplayName)) {
sb.Append("|");
sb.Append(u.DisplayName);
}
sb.Append("\r\n");
}
File.WriteAllText(GetFullPath(UsersFile), sb.ToString());
}
}
/// <summary>
/// Finds a user group.
/// </summary>
/// <param name="name">The name of the group to find.</param>
/// <returns>The <see cref="T:UserGroup" /> or <c>null</c> if no data is found.</returns>
private UserGroup FindGroup(string name) {
lock(this) {
UserGroup[] allUsers = GetUserGroups();
UserGroupComparer comp = new UserGroupComparer();
UserGroup target = new UserGroup(name, "", this);
foreach(UserGroup g in allUsers) {
if(comp.Compare(g, target) == 0) return g;
}
return null;
}
}
/// <summary>
/// Gets all the user groups.
/// </summary>
/// <returns>All the groups, sorted by name.</returns>
public UserGroup[] GetUserGroups() {
lock(this) {
if(groupsCache == null) {
string[] lines = File.ReadAllLines(GetFullPath(GroupsFile));
UserGroup[] result = new UserGroup[lines.Length];
string[] fields;
string[] users;
for(int count = 0; count < lines.Length; count++) {
// Structure - description can be empty
// Name|Description|User1|User2|...
fields = lines[count].Split('|');
users = new string[fields.Length - 2];
for(int i = 0; i < fields.Length - 2; i++) {
users[i] = fields[i + 2];
}
result[count] = new UserGroup(fields[0], fields[1], this);
result[count].Users = users;
}
Array.Sort(result, new UserGroupComparer());
groupsCache = result;
}
return groupsCache;
}
}
/// <summary>
/// Adds a new user group.
/// </summary>
/// <param name="name">The name of the group.</param>
/// <param name="description">The description of the group.</param>
/// <returns>The correct <see cref="T:UserGroup"/> object or <c>null</c>.</returns>
/// <exception cref="ArgumentNullException">If <b>name</b> or <b>description</b> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>name</b> is empty.</exception>
public UserGroup AddUserGroup(string name, string description) {
if(name == null) throw new ArgumentNullException("name");
if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name");
if(description == null) throw new ArgumentNullException("description");
lock(this) {
if(FindGroup(name) != null) return null;
BackupGroupsFile();
groupsCache = null;
// Structure - description can be empty
// Name|Description|User1|User2|...
File.AppendAllText(GetFullPath(GroupsFile),
name + "|" + description + "\r\n");
return new UserGroup(name, description, this);
}
}
private void BackupGroupsFile() {
lock(this) {
File.Copy(GetFullPath(GroupsFile),
GetFullPath(Path.GetFileNameWithoutExtension(GroupsFile) +
".bak" + Path.GetExtension(GroupsFile)), true);
}
}
/// <summary>
/// Dumps user groups on disk.
/// </summary>
/// <param name="groups">The user groups to dump.</param>
private void DumpUserGroups(UserGroup[] groups) {
lock(this) {
StringBuilder sb = new StringBuilder(1000);
foreach(UserGroup group in groups) {
// Structure - description can be empty
// Name|Description|User1|User2|...
sb.Append(group.Name);
sb.Append("|");
sb.Append(group.Description);
if(group.Users.Length > 0) {
foreach(string user in group.Users) {
sb.Append("|");
sb.Append(user);
}
}
sb.Append("\r\n");
}
BackupGroupsFile();
File.WriteAllText(GetFullPath(GroupsFile), sb.ToString());
}
}
/// <summary>
/// Modifies a user group.
/// </summary>
/// <param name="group">The group to modify.</param>
/// <param name="description">The new description of the group.</param>
/// <returns>The correct <see cref="T:UserGroup"/> object or <c>null</c>.</returns>
/// <exception cref="ArgumentNullException">If <b>group</b> or <b>description</b> are <c>null</c>.</exception>
public UserGroup ModifyUserGroup(UserGroup group, string description) {
if(group == null) throw new ArgumentNullException("group");
if(description == null) throw new ArgumentNullException("description");
lock(this) {
UserGroup[] allGroups = GetUserGroups();
groupsCache = null;
UserGroupComparer comp = new UserGroupComparer();
for(int i = 0; i < allGroups.Length; i++) {
if(comp.Compare(allGroups[i], group) == 0) {
allGroups[i].Description = description;
DumpUserGroups(allGroups);
return allGroups[i];
}
}
return null;
}
}
/// <summary>
/// Removes a user group.
/// </summary>
/// <param name="group">The group to remove.</param>
/// <returns><c>true</c> if the group is removed, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <b>group</b> is <c>null</c>.</exception>
public bool RemoveUserGroup(UserGroup group) {
if(group == null) throw new ArgumentNullException("group");
lock(this) {
UserGroup[] allGroups = GetUserGroups();
List<UserGroup> result = new List<UserGroup>(allGroups.Length);
UserGroupComparer comp = new UserGroupComparer();
foreach(UserGroup g in allGroups) {
if(comp.Compare(g, group) != 0) {
result.Add(g);
}
}
DumpUserGroups(result.ToArray());
groupsCache = null;
return result.Count == allGroups.Length - 1;
}
}
/// <summary>
/// Sets the group memberships of a user account.
/// </summary>
/// <param name="user">The user account.</param>
/// <param name="groups">The groups the user account is member of.</param>
/// <returns>The correct <see cref="T:UserGroup"/> object or <c>null</c>.</returns>
/// <exception cref="ArgumentNullException">If <b>user</b> or <b>groups</b> are <c>null</c>.</exception>
public UserInfo SetUserMembership(UserInfo user, string[] groups) {
if(user == null) throw new ArgumentNullException("user");
if(groups == null) throw new ArgumentNullException("groups");
lock(this) {
foreach(string g in groups) {
if(FindGroup(g) == null) return null;
}
LocalUserInfo local = LoadLocalInstance(user);
if(local == null) return null;
UserGroup[] allGroups = GetUserGroups();
List<string> users;
for(int i = 0; i < allGroups.Length; i++) {
users = new List<string>(allGroups[i].Users);
if(IsSelected(allGroups[i], groups)) {
// Current group is one of the selected, add user to it
if(!users.Contains(user.Username)) users.Add(user.Username);
}
else {
// Current group is not bound with the user, remove user
users.Remove(user.Username);
}
allGroups[i].Users = users.ToArray();
}
groupsCache = null;
usersCache = null;
DumpUserGroups(allGroups);
LocalUserInfo result = new LocalUserInfo(local.Username, local.DisplayName, local.Email, local.Active,
local.DateTime, this, local.PasswordHash);
result.Groups = groups;
return result;
}
}
/// <summary>
/// Determines whether a user group is contained in an array of user group names.
/// </summary>
/// <param name="group">The user group to check.</param>
/// <param name="groups">The user group names array.</param>
/// <returns><c>true</c> if <b>users</b> contains <b>user.Name</b>, <c>false</c> otherwise.</returns>
private static bool IsSelected(UserGroup group, string[] groups) {
StringComparer comp = StringComparer.OrdinalIgnoreCase;
return Array.Find(groups, delegate(string g) { return comp.Compare(g, group.Name) == 0; }) != null;
}
/// <summary>
/// Tries to login a user directly through the provider.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
/// <returns>The correct UserInfo object, or <c>null</c>.</returns>
/// <exception cref="ArgumentNullException">If <b>username</b> or <b>password</b> are <c>null</c>.</exception>
public UserInfo TryManualLogin(string username, string password) {
if(username == null) throw new ArgumentNullException("username");
if(password == null) throw new ArgumentNullException("password");
// Shortcut
if(username.Length == 0) return null;
if(password.Length == 0) return null;
lock(this) {
string hash = Hash.Compute(password);
UserInfo[] all = GetUsers();
foreach(UserInfo u in all) {
if(u.Active &&
//string.Compare(u.Username, username, false, System.Globalization.CultureInfo.InvariantCulture) == 0 &&
//string.Compare(((LocalUserInfo)u).PasswordHash, hash, false, System.Globalization.CultureInfo.InvariantCulture) == 0) {
string.CompareOrdinal(u.Username, username) == 0 &&
string.CompareOrdinal(((LocalUserInfo)u).PasswordHash, hash) == 0) {
return u;
}
}
}
return null;
}
/// <summary>
/// Tries to login a user directly through the provider using
/// the current HttpContext and without username/password.
/// </summary>
/// <param name="context">The current HttpContext.</param>
/// <returns>The correct UserInfo object, or <c>null</c>.</returns>
/// <exception cref="ArgumentNullException">If <b>context</b> is <c>null</c>.</exception>
public UserInfo TryAutoLogin(System.Web.HttpContext context) {
if(context == null) throw new ArgumentNullException("context");
return null;
}
/// <summary>
/// Tries to retrieve the information about a user account.
/// </summary>
/// <param name="username">The username.</param>
/// <returns>The correct UserInfo object, or <c>null</c>.</returns>
/// <exception cref="ArgumentNullException">If <b>username</b> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>username</b> is empty.</exception>
public UserInfo GetUser(string username) {
if(username == null) throw new ArgumentNullException("username");
if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username");
lock(this) {
UserInfo[] all = GetUsers();
foreach(UserInfo u in all) {
if(string.Compare(u.Username, username, false, System.Globalization.CultureInfo.InvariantCulture) == 0) {
return u;
}
}
}
return null;
}
/// <summary>
/// Tries to retrieve the information about a user account.
/// </summary>
/// <param name="email">The email address.</param>
/// <returns>The first user found with the specified email address, or <c>null</c>.</returns>
/// <exception cref="ArgumentNullException">If <b>email</b> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>email</b> is empty.</exception>
public UserInfo GetUserByEmail(string email) {
if(email == null) throw new ArgumentNullException("email");
if(email.Length == 0) throw new ArgumentException("Email cannot be empty", "email");
lock(this) {
foreach(UserInfo user in GetUsers()) {
if(user.Email == email) return user;
}
}
return null;
}
/// <summary>
/// Notifies the provider that a user has logged in through the authentication cookie.
/// </summary>
/// <param name="user">The user who has logged in.</param>
/// <exception cref="ArgumentNullException">If <b>user</b> is <c>null</c>.</exception>
public void NotifyCookieLogin(UserInfo user) {
if(user == null) throw new ArgumentNullException("user");
// Nothing to do
}
/// <summary>
/// Notifies the provider that a user has logged out.
/// </summary>
/// <param name="user">The user who has logged out.</param>
/// <exception cref="ArgumentNullException">If <b>user</b> is <c>null</c>.</exception>
public void NotifyLogout(UserInfo user) {
if(user == null) throw new ArgumentNullException("user");
// Nothing to do
}
/// <summary>
/// Stores a user data element, overwriting the previous one if present.
/// </summary>
/// <param name="user">The user the data belongs to.</param>
/// <param name="key">The key of the data element (case insensitive).</param>
/// <param name="value">The value of the data element, <c>null</c> for deleting the data.</param>
/// <returns><c>true</c> if the data element is stored, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentNullException">If <b>user</b> or <b>key</b> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>key</b> is empty.</exception>
public bool StoreUserData(UserInfo user, string key, string value) {
if(user == null) throw new ArgumentNullException("user");
if(key == null) throw new ArgumentNullException("key");
if(key.Length == 0) throw new ArgumentException("Key cannot be empty", "key");
// Format
// User|Key|Value
lock(this) {
if(GetUser(user.Username) == null) return false;
// Find a previously existing key and replace it if found
// If not found, add a new line
string lowercaseUsername = user.Username.ToLowerInvariant();
string lowercaseKey = key.ToLowerInvariant();
string[] lines = File.ReadAllLines(GetFullPath(UsersDataFile));
string[] fields;
for(int i = 0; i < lines.Length; i++) {
fields = lines[i].Split('|');
if(fields[0].ToLowerInvariant() == lowercaseUsername && fields[1].ToLowerInvariant() == lowercaseKey) {
if(value != null) {
// Replace the value, then save file
lines[i] = fields[0] + "|" + fields[1] + "|" + value;
}
else {
// Remove the element
string[] newLines = new string[lines.Length - 1];
Array.Copy(lines, newLines, i);
Array.Copy(lines, i + 1, newLines, i, lines.Length - i - 1);
lines = newLines;
}
File.WriteAllLines(GetFullPath(UsersDataFile), lines);
return true;
}
}
// If the program gets here, the element was not present, append it
File.AppendAllText(GetFullPath(UsersDataFile), user.Username + "|" + key + "|" + value + "\r\n");
return true;
}
}
/// <summary>
/// Gets a user data element, if any.
/// </summary>
/// <param name="user">The user the data belongs to.</param>
/// <param name="key">The key of the data element.</param>
/// <returns>The value of the data element, or <c>null</c> if the element is not found.</returns>
/// <exception cref="ArgumentNullException">If <b>user</b> or <b>key</b> are <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>key</b> is empty.</exception>
public string RetrieveUserData(UserInfo user, string key) {
if(user == null) throw new ArgumentNullException("user");
if(key == null) throw new ArgumentNullException("key");
if(key.Length == 0) throw new ArgumentException("Key cannot be empty", "key");
lock(this) {
string lowercaseUsername = user.Username.ToLowerInvariant();
string lowercaseKey = key.ToLowerInvariant();
string[] lines = File.ReadAllLines(GetFullPath(UsersDataFile));
string[] fields;
foreach(string line in lines) {
fields = line.Split('|');
if(fields[0].ToLowerInvariant() == lowercaseUsername && fields[1].ToLowerInvariant() == lowercaseKey) {
return fields[2];
}
}
}
return null;
}
/// <summary>
/// Retrieves all the user data elements for a user.
/// </summary>
/// <param name="user">The user.</param>
/// <returns>The user data elements (key-&gt;value).</returns>
/// <exception cref="ArgumentNullException">If <b>user</b> is <c>null</c>.</exception>
public IDictionary<string, string> RetrieveAllUserData(UserInfo user) {
if(user == null) throw new ArgumentNullException("user");
lock(this) {
string lowercaseUsername = user.Username.ToLowerInvariant();
string[] lines = File.ReadAllLines(GetFullPath(UsersDataFile));
Dictionary<string, string> result = new Dictionary<string, string>(10);
string[] fields;
foreach(string line in lines) {
fields = line.Split('|');
if(fields[0].ToLowerInvariant() == lowercaseUsername) {
result.Add(fields[1], fields[2]);
}
}
return result;
}
}
/// <summary>
/// Gets all the users that have the specified element in their data.
/// </summary>
/// <param name="key">The key of the data.</param>
/// <returns>The users and the data.</returns>
/// <exception cref="ArgumentNullException">If <b>key</b> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">If <b>key</b> is empty.</exception>
public IDictionary<UserInfo, string> GetUsersWithData(string key) {
if(key == null) throw new ArgumentNullException("key");
if(key.Length == 0) throw new ArgumentException("Key cannot be empty", "key");
lock(this) {
UserInfo[] allUsers = GetUsers();
string[] lines = File.ReadAllLines(GetFullPath(UsersDataFile));
Dictionary<UserInfo, string> result = new Dictionary<UserInfo, string>(lines.Length / 4);
string[] fields;
foreach(string line in lines) {
fields = line.Split('|');
if(fields[1] == key) {
UserInfo currentUser = Array.Find(allUsers, delegate(UserInfo user) {
return user.Username == fields[0];
});
if(currentUser != null) result.Add(currentUser, fields[2]);
}
}
return result;
}
}
}
}

64
Documentation.shfb Normal file
View file

@ -0,0 +1,64 @@
<project schemaVersion="1.6.0.7">
<assemblies>
<assembly assemblyPath=".\PluginPack\bin\Release\PluginPack.dll" xmlCommentsPath=".\PluginPack\bin\Release\PluginPack.xml" commentsOnly="False" />
<assembly assemblyPath=".\AclEngine\bin\Release\ScrewTurn.Wiki.AclEngine.dll" xmlCommentsPath=".\AclEngine\bin\Release\ScrewTurn.Wiki.AclEngine.xml" commentsOnly="False" />
<assembly assemblyPath=".\Core\bin\Release\ScrewTurn.Wiki.Core.dll" xmlCommentsPath=".\Core\bin\Release\ScrewTurn.Wiki.Core.xml" commentsOnly="False" />
<assembly assemblyPath=".\Core\bin\Release\ScrewTurn.Wiki.PluginFramework.dll" xmlCommentsPath=".\Core\bin\Release\ScrewTurn.Wiki.PluginFramework.xml" commentsOnly="False" />
<assembly assemblyPath=".\SqlProvidersCommon\bin\Release\ScrewTurn.Wiki.Plugins.SqlCommon.dll" xmlCommentsPath=".\SqlProvidersCommon\bin\Release\ScrewTurn.Wiki.Plugins.SqlCommon.xml" commentsOnly="False" />
<assembly assemblyPath=".\SearchEngine\bin\Release\ScrewTurn.Wiki.SearchEngine.dll" xmlCommentsPath=".\SearchEngine\bin\Release\ScrewTurn.Wiki.SearchEngine.xml" commentsOnly="False" />
<assembly assemblyPath=".\SqlServerProviders\bin\Release\SqlServerProviders.dll" xmlCommentsPath=".\SqlServerProviders\bin\Release\SqlServerProviders.xml" commentsOnly="False" />
</assemblies>
<namespaceSummaries>
<namespaceSummaryItem name="" isDocumented="False" />
<namespaceSummaryItem name="ScrewTurn.Wiki" isDocumented="True">Namespace containing the ScrewTurn Wiki engine.</namespaceSummaryItem>
<namespaceSummaryItem name="ScrewTurn.Wiki.AclEngine" isDocumented="True">Namespace containing objects that implement the ACL Engine.</namespaceSummaryItem>
<namespaceSummaryItem name="ScrewTurn.Wiki.ImportWiki" isDocumented="True">Namespace containing objects that are used to import data from other wiki engines.</namespaceSummaryItem>
<namespaceSummaryItem name="ScrewTurn.Wiki.PluginFramework" isDocumented="True">Namespace containing base types used to build the ScrewTurn Wiki engine core and plugins.</namespaceSummaryItem>
<namespaceSummaryItem name="ScrewTurn.Wiki.PluginPack" isDocumented="True" />
<namespaceSummaryItem name="ScrewTurn.Wiki.Plugins.PluginPack" isDocumented="True">Namespace containing generic helper providers.</namespaceSummaryItem>
<namespaceSummaryItem name="ScrewTurn.Wiki.Plugins.SqlCommon" isDocumented="True">Namespace containing base types used to build the SQL-based storage providers.</namespaceSummaryItem>
<namespaceSummaryItem name="ScrewTurn.Wiki.Plugins.SqlServer" isDocumented="True">Namespace containing SQL Server storage providers.</namespaceSummaryItem>
<namespaceSummaryItem name="ScrewTurn.Wiki.SearchEngine" isDocumented="True">Namespace containing base types used for building an extensible index-based search engine.</namespaceSummaryItem>
</namespaceSummaries>
<ProjectSummary>ScrewTurn Wiki documentation.</ProjectSummary>
<MissingTags>Summary, Parameter, Returns, AutoDocumentCtors, Namespace, TypeParameter</MissingTags>
<VisibleItems>Attributes, InheritedMembers, InheritedFrameworkMembers, Protected, SealedProtected</VisibleItems>
<HtmlHelp1xCompilerPath path="%ProgramFiles%\HTML Help Workshop\" />
<HtmlHelp2xCompilerPath path="" />
<OutputPath>.\Help\</OutputPath>
<SandcastlePath path="%ProgramFiles%\Sandcastle\" />
<WorkingPath path="" />
<CleanIntermediates>True</CleanIntermediates>
<KeepLogFile>True</KeepLogFile>
<BuildLogFile path="" />
<HelpFileFormat>HtmlHelp1x</HelpFileFormat>
<CppCommentsFixup>False</CppCommentsFixup>
<FrameworkVersion>2.0.50727</FrameworkVersion>
<IndentHtml>False</IndentHtml>
<Preliminary>True</Preliminary>
<RootNamespaceContainer>False</RootNamespaceContainer>
<RootNamespaceTitle />
<HelpTitle>ScrewTurn Wiki Developer's Documentation</HelpTitle>
<HtmlHelpName>ScrewTurnWiki</HtmlHelpName>
<Language>en-US</Language>
<CopyrightHref>http://www.screwturn.eu</CopyrightHref>
<CopyrightText>Copyright 2006-2009 Dario Solera</CopyrightText>
<FeedbackEMailAddress />
<FeedbackEMailLinkText />
<HeaderText>ScrewTurn Wiki Developer's Documentation</HeaderText>
<FooterText />
<ProjectLinkType>Local</ProjectLinkType>
<SdkLinkType>Msdn</SdkLinkType>
<SdkLinkTarget>Blank</SdkLinkTarget>
<PresentationStyle>Prototype</PresentationStyle>
<NamingMethod>Guid</NamingMethod>
<SyntaxFilters>Standard</SyntaxFilters>
<ShowFeedbackControl>False</ShowFeedbackControl>
<BinaryTOC>True</BinaryTOC>
<IncludeFavorites>False</IncludeFavorites>
<CollectionTocStyle>Hierarchical</CollectionTocStyle>
<IncludeStopWordList>True</IncludeStopWordList>
<PlugInNamespaces>ms.vsipcc+, ms.vsexpresscc+</PlugInNamespaces>
<HelpFileVersion>3.0.0.149</HelpFileVersion>
<ContentPlacement>BelowNamespaces</ContentPlacement>
</project>

BIN
Help/ScrewTurnWiki.chm Normal file

Binary file not shown.

7
Install - Readme.txt Normal file
View file

@ -0,0 +1,7 @@
In order to install and run ScrewTurn Wiki, you must first compile the application as described
in the Build directory, then follow the instructions that are generated along with the binary packages.
Alternatively, you can download the appropriate compiled package from the application website [1].
[1] http://www.screwturn.eu

Some files were not shown because too many files have changed in this diff Show more