Files
Umbraco-CMS/src/Umbraco.Core/Packaging/PackageInstallation.cs
2014-06-16 12:12:59 +02:00

602 lines
27 KiB
C#

using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Packaging.Models;
using Umbraco.Core.Services;
namespace Umbraco.Core.Packaging
{
internal class PackageInstallation : IPackageInstallation
{
private readonly IFileService _fileService;
private readonly IMacroService _macroService;
private readonly IPackagingService _packagingService;
private IConflictingPackageContentFinder _conflictingPackageContentFinder;
private readonly IPackageExtraction _packageExtraction;
public PackageInstallation(IPackagingService packagingService, IMacroService macroService,
IFileService fileService, IPackageExtraction packageExtraction)
{
if (packageExtraction != null) _packageExtraction = packageExtraction;
else throw new ArgumentNullException("packageExtraction");
if (macroService != null) _macroService = macroService;
else throw new ArgumentNullException("macroService");
if (fileService != null) _fileService = fileService;
else throw new ArgumentNullException("fileService");
if (packagingService != null) _packagingService = packagingService;
else throw new ArgumentNullException("packagingService");
}
public IConflictingPackageContentFinder ConflictingPackageContentFinder
{
private get
{
return _conflictingPackageContentFinder ??
(_conflictingPackageContentFinder = new ConflictingPackageContentFinder(_macroService, _fileService));
}
set
{
if (_conflictingPackageContentFinder != null)
{
throw new PropertyConstraintException("This property already have a value");
}
_conflictingPackageContentFinder = value;
}
}
private string _fullpathToRoot;
public string FullpathToRoot
{
private get { return _fullpathToRoot ?? (_fullpathToRoot = GlobalSettings.FullpathToRoot); }
set
{
if (_fullpathToRoot != null)
{
throw new PropertyConstraintException("This property already have a value");
}
_fullpathToRoot = value;
}
}
public MetaData GetMetaData(string packageFilePath)
{
try
{
XElement rootElement = GetConfigXmlElement(packageFilePath);
return GetMetaData(rootElement);
}
catch (Exception e)
{
throw new Exception("Error reading " + packageFilePath, e);
}
}
public PreInstallWarnings GetPreInstallWarnings(string packageFilePath)
{
try
{
XElement rootElement = GetConfigXmlElement(packageFilePath);
return GetPreInstallWarnings(rootElement);
}
catch (Exception e)
{
throw new Exception("Error reading " + packageFilePath, e);
}
}
public InstallationSummary InstallPackage(string packageFile, int userId)
{
XElement dataTypes;
XElement languages;
XElement dictionaryItems;
XElement macroes;
XElement files;
XElement templates;
XElement documentTypes;
XElement styleSheets;
XElement documentSet;
XElement actions;
MetaData metaData;
InstallationSummary installationSummary;
try
{
XElement rootElement = GetConfigXmlElement(packageFile);
PackageSupportedCheck(rootElement);
PackageStructureSanetyCheck(packageFile, rootElement);
dataTypes = rootElement.Element(Constants.Packaging.DataTypesNodeName);
languages = rootElement.Element(Constants.Packaging.LanguagesNodeName);
dictionaryItems = rootElement.Element(Constants.Packaging.DictionaryItemsNodeName);
macroes = rootElement.Element(Constants.Packaging.MacrosNodeName);
files = rootElement.Element(Constants.Packaging.FilesNodeName);
templates = rootElement.Element(Constants.Packaging.TemplatesNodeName);
documentTypes = rootElement.Element(Constants.Packaging.DocumentTypesNodeName);
styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName);
documentSet = rootElement.Element(Constants.Packaging.DocumentSetNodeName);
actions = rootElement.Element(Constants.Packaging.ActionsNodeName);
metaData = GetMetaData(rootElement);
installationSummary = new InstallationSummary() { MetaData = metaData };
}
catch (Exception e)
{
throw new Exception("Error reading " + packageFile, e);
}
try
{
var dataTypeDefinitions = EmptyArrayIfNull<IDataTypeDefinition>(dataTypes) ?? InstallDataTypes(dataTypes, userId);
installationSummary.DataTypesInstalled = dataTypeDefinitions;
var languagesInstalled = EmptyArrayIfNull<ILanguage>(languages) ?? InstallLanguages(languages, userId);
installationSummary.LanguagesInstalled = languagesInstalled;
var dictionaryInstalled = EmptyArrayIfNull<IDictionaryItem>(dictionaryItems) ?? InstallDictionaryItems(dictionaryItems);
installationSummary.DictionaryItemsInstalled = dictionaryInstalled;
var macros = EmptyArrayIfNull<IMacro>(macroes)?? InstallMacros(macroes, userId);
installationSummary.MacrosInstalled = macros;
var keyValuePairs = EmptyArrayIfNull<Details<string>>(packageFile) ?? InstallFiles(packageFile, files);
installationSummary.FilesInstalled = keyValuePairs;
var templatesInstalled = EmptyArrayIfNull<ITemplate>(templates) ?? InstallTemplats(templates, userId);
installationSummary.TemplatesInstalled = templatesInstalled;
var documentTypesInstalled = EmptyArrayIfNull<IContentType>(documentTypes) ?? InstallDocumentTypes(documentTypes, userId);
installationSummary.DocumentTypesInstalled =documentTypesInstalled;
var stylesheetsInstalled = EmptyArrayIfNull<IStylesheet>(styleSheets) ?? InstallStylesheets(styleSheets, userId);
installationSummary.StylesheetsInstalled = stylesheetsInstalled;
var documentsInstalled = EmptyArrayIfNull<IContent>(documentSet) ?? InstallDocuments(documentSet, userId);
installationSummary.DocumentsInstalled = documentsInstalled;
var packageActions = EmptyArrayIfNull<PackageAction>(actions) ?? GetPackageActions(actions, metaData.Name);
installationSummary.Actions = packageActions;
return installationSummary;
}
catch (Exception e)
{
throw new Exception("Error installing package " + packageFile, e);
}
}
/// <summary>
/// Temperary check to test that we support stylesheets
/// </summary>
/// <param name="rootElement"></param>
private void PackageSupportedCheck(XElement rootElement)
{
XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName);
if (styleSheets != null && styleSheets.Elements().Any())
throw new NotSupportedException("Stylesheets is not suported in this version of umbraco");
}
private static T[] EmptyArrayIfNull<T>(object obj)
{
return obj == null ? new T[0] : null;
}
private XDocument GetConfigXmlDoc(string packageFilePath)
{
string filePathInPackage;
string configXmlContent = _packageExtraction.ReadTextFileFromArchive(packageFilePath,
Constants.Packaging.PackageXmlFileName, out filePathInPackage);
return XDocument.Parse(configXmlContent);
}
private XElement GetConfigXmlElement(string packageFilePath)
{
XDocument document = GetConfigXmlDoc(packageFilePath);
if (document.Root == null ||
document.Root.Name.LocalName.Equals(Constants.Packaging.UmbPackageNodeName) == false)
{
throw new ArgumentException("xml does not have a root node called \"umbPackage\"", packageFilePath);
}
return document.Root;
}
internal void PackageStructureSanetyCheck(string packageFilePath)
{
XElement rootElement = GetConfigXmlElement(packageFilePath);
PackageStructureSanetyCheck(packageFilePath, rootElement);
}
private void PackageStructureSanetyCheck(string packageFilePath, XElement rootElement)
{
XElement filesElement = rootElement.Element(Constants.Packaging.FilesNodeName);
if (filesElement != null)
{
IEnumerable<FileInPackageInfo> extractFileInPackageInfos =
ExtractFileInPackageInfos(filesElement).ToArray();
IEnumerable<string> missingFiles =
_packageExtraction.FindMissingFiles(packageFilePath,
extractFileInPackageInfos.Select(i => i.FileNameInPackage)).ToArray();
if (missingFiles.Any())
{
throw new Exception("The following file(s) are missing in the package: " +
string.Join(", ", missingFiles.Select(
mf =>
{
FileInPackageInfo fileInPackageInfo =
extractFileInPackageInfos.Single(fi => fi.FileNameInPackage == mf);
return string.Format("Guid: \"{0}\" Original File: \"{1}\"",
fileInPackageInfo.FileNameInPackage, fileInPackageInfo.RelativePath);
})));
}
IEnumerable<string> dubletFileNames = _packageExtraction.FindDubletFileNames(packageFilePath).ToArray();
if (dubletFileNames.Any())
{
throw new Exception("The following filename(s) are found more than one time in the package, since the filename is used ad primary key, this is not allowed: " +
string.Join(", ", dubletFileNames));
}
}
}
private static PackageAction[] GetPackageActions(XElement actionsElement, string packageName)
{
if (actionsElement == null) { return new PackageAction[0]; }
if (string.Equals(Constants.Packaging.ActionsNodeName, actionsElement.Name.LocalName) == false)
{
throw new ArgumentException("Must be \"" + Constants.Packaging.ActionsNodeName + "\" as root",
"actionsElement");
}
return actionsElement.Elements(Constants.Packaging.ActionNodeName)
.Select(elemet =>
{
XAttribute aliasAttr = elemet.Attribute(Constants.Packaging.AliasNodeNameCapital);
if (aliasAttr == null)
throw new ArgumentException(
"missing \"" + Constants.Packaging.AliasNodeNameCapital + "\" atribute in alias element",
"actionsElement");
var packageAction = new PackageAction
{
XmlData = elemet,
Alias = aliasAttr.Value,
PackageName = packageName,
};
XAttribute attr = elemet.Attribute(Constants.Packaging.RunatNodeAttribute);
ActionRunAt runAt;
if (attr != null && Enum.TryParse(attr.Value, true, out runAt)) { packageAction.RunAt = runAt; }
attr = elemet.Attribute(Constants.Packaging.UndoNodeAttribute);
bool undo;
if (attr != null && bool.TryParse(attr.Value, out undo)) { packageAction.Undo = undo; }
return packageAction;
}).ToArray();
}
private IContent[] InstallDocuments(XElement documentsElement, int userId = 0)
{
if (string.Equals(Constants.Packaging.DocumentSetNodeName, documentsElement.Name.LocalName) == false)
{
throw new ArgumentException("Must be \"" + Constants.Packaging.DocumentSetNodeName + "\" as root",
"documentsElement");
}
return _packagingService.ImportContent(documentsElement, -1, userId).ToArray();
}
private IStylesheet[] InstallStylesheets(XElement styleSheetsElement, int userId = 0)
{
if (string.Equals(Constants.Packaging.StylesheetsNodeName, styleSheetsElement.Name.LocalName) == false)
{
throw new ArgumentException("Must be \"" + Constants.Packaging.StylesheetsNodeName + "\" as root",
"styleSheetsElement");
}
return _packagingService.ImportStylesheets(styleSheetsElement, userId).ToArray();
}
private IContentType[] InstallDocumentTypes(XElement documentTypes, int userId = 0)
{
if (string.Equals(Constants.Packaging.DocumentTypesNodeName, documentTypes.Name.LocalName) == false)
{
if (string.Equals(Constants.Packaging.DocumentTypeNodeName, documentTypes.Name.LocalName) == false)
throw new ArgumentException(
"Must be \"" + Constants.Packaging.DocumentTypesNodeName + "\" as root", "documentTypes");
documentTypes = new XElement(Constants.Packaging.DocumentTypesNodeName, documentTypes);
}
return _packagingService.ImportContentTypes(documentTypes, userId).ToArray();
}
private ITemplate[] InstallTemplats(XElement templateElement, int userId = 0)
{
if (string.Equals(Constants.Packaging.TemplatesNodeName, templateElement.Name.LocalName) == false)
{
throw new ArgumentException("Must be \"" + Constants.Packaging.TemplatesNodeName + "\" as root",
"templateElement");
}
return _packagingService.ImportTemplates(templateElement, userId).ToArray();
}
private Details<string>[] InstallFiles(string packageFilePath, XElement filesElement)
{
return ExtractFileInPackageInfos(filesElement).Select(fpi =>
{
bool existingOverrided = _packageExtraction.CopyFileFromArchive(packageFilePath, fpi.FileNameInPackage,
fpi.FullPath);
return new Details<string>()
{
Source = fpi.FileNameInPackage,
Destination = fpi.FullPath,
Status = existingOverrided ? InstallStatus.Overwridden : InstallStatus.Inserted
};
}).ToArray();
}
private IMacro[] InstallMacros(XElement macroElements, int userId = 0)
{
if (string.Equals(Constants.Packaging.MacrosNodeName, macroElements.Name.LocalName) == false)
{
throw new ArgumentException("Must be \"" + Constants.Packaging.MacrosNodeName + "\" as root",
"macroElements");
}
return _packagingService.ImportMacros(macroElements, userId).ToArray();
}
private IDictionaryItem[] InstallDictionaryItems(XElement dictionaryItemsElement)
{
if (string.Equals(Constants.Packaging.DictionaryItemsNodeName, dictionaryItemsElement.Name.LocalName) ==
false)
{
throw new ArgumentException("Must be \"" + Constants.Packaging.DictionaryItemsNodeName + "\" as root",
"dictionaryItemsElement");
}
return _packagingService.ImportDictionaryItems(dictionaryItemsElement).ToArray();
}
private ILanguage[] InstallLanguages(XElement languageElement, int userId = 0)
{
if (string.Equals(Constants.Packaging.LanguagesNodeName, languageElement.Name.LocalName) == false)
{
throw new ArgumentException("Must be \"" + Constants.Packaging.LanguagesNodeName + "\" as root", "languageElement");
}
return _packagingService.ImportLanguages(languageElement, userId).ToArray();
}
private IDataTypeDefinition[] InstallDataTypes(XElement dataTypeElements, int userId = 0)
{
if (string.Equals(Constants.Packaging.DataTypesNodeName, dataTypeElements.Name.LocalName) == false)
{
if (string.Equals(Constants.Packaging.DataTypeNodeName, dataTypeElements.Name.LocalName) == false)
{
throw new ArgumentException("Must be \"" + Constants.Packaging.DataTypeNodeName + "\" as root", "dataTypeElements");
}
}
return _packagingService.ImportDataTypeDefinitions(dataTypeElements, userId).ToArray();
}
private PreInstallWarnings GetPreInstallWarnings(XElement rootElement)
{
XElement files = rootElement.Element(Constants.Packaging.FilesNodeName);
XElement styleSheets = rootElement.Element(Constants.Packaging.StylesheetsNodeName);
XElement templates = rootElement.Element(Constants.Packaging.TemplatesNodeName);
XElement alias = rootElement.Element(Constants.Packaging.MacrosNodeName);
var conflictingPackageContent = new PreInstallWarnings
{
UnsecureFiles = files == null ? new IFileInPackageInfo[0] : FindUnsecureFiles(files),
ConflictingMacroAliases = alias == null ? new IMacro[0] : ConflictingPackageContentFinder.FindConflictingMacros(alias),
ConflictingTemplateAliases =
templates == null ? new ITemplate[0] : ConflictingPackageContentFinder.FindConflictingTemplates(templates),
ConflictingStylesheetNames =
styleSheets == null ? new IStylesheet[0] : ConflictingPackageContentFinder.FindConflictingStylesheets(styleSheets)
};
return conflictingPackageContent;
}
private IFileInPackageInfo[] FindUnsecureFiles(XElement fileElement)
{
return ExtractFileInPackageInfos(fileElement)
.Where(IsFileNodeUnsecure).Cast<IFileInPackageInfo>().ToArray();
}
private bool IsFileNodeUnsecure(FileInPackageInfo fileInPackageInfo)
{
// Should be done with regex :)
if (fileInPackageInfo.Directory.ToLower().Contains(IOHelper.DirSepChar + "app_code")) return true;
if (fileInPackageInfo.Directory.ToLower().Contains(IOHelper.DirSepChar + "bin")) return true;
string extension = Path.GetExtension(fileInPackageInfo.Directory);
return extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase);
}
private IEnumerable<FileInPackageInfo> ExtractFileInPackageInfos(XElement filesElement)
{
if (string.Equals(Constants.Packaging.FilesNodeName, filesElement.Name.LocalName) == false)
{
throw new ArgumentException("the root element must be \"Files\"", "filesElement");
}
return filesElement.Elements(Constants.Packaging.FileNodeName)
.Select(e =>
{
XElement guidElement = e.Element(Constants.Packaging.GuidNodeName);
if (guidElement == null)
{
throw new ArgumentException("Missing element \"" + Constants.Packaging.GuidNodeName + "\"",
"filesElement");
}
XElement orgPathElement = e.Element(Constants.Packaging.OrgPathNodeName);
if (orgPathElement == null)
{
throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgPathNodeName + "\"",
"filesElement");
}
XElement orgNameElement = e.Element(Constants.Packaging.OrgNameNodeName);
if (orgNameElement == null)
{
throw new ArgumentException("Missing element \"" + Constants.Packaging.OrgNameNodeName + "\"",
"filesElement");
}
return new FileInPackageInfo
{
FileNameInPackage = guidElement.Value,
FileName = PrepareAsFilePathElement(orgNameElement.Value),
RelativeDir = UpdatePathPlaceholders(
PrepareAsFilePathElement(orgPathElement.Value)),
DestinationRootDir = FullpathToRoot
};
}).ToArray();
}
private static string PrepareAsFilePathElement(string pathElement)
{
return pathElement.TrimStart(new[] {'\\', '/', '~'}).Replace("/", "\\");
}
private MetaData GetMetaData(XElement xRootElement)
{
XElement infoElement = xRootElement.Element(Constants.Packaging.InfoNodeName);
if (infoElement == null)
{
throw new ArgumentException("Did not hold a \"" + Constants.Packaging.InfoNodeName + "\" element",
"xRootElement");
}
var majorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMajorXpath);
var minorElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsMinorXpath);
var patchElement = infoElement.XPathSelectElement(Constants.Packaging.PackageRequirementsPatchXpath);
var nameElement = infoElement.XPathSelectElement(Constants.Packaging.PackageNameXpath);
var versionElement = infoElement.XPathSelectElement(Constants.Packaging.PackageVersionXpath);
var urlElement = infoElement.XPathSelectElement(Constants.Packaging.PackageUrlXpath);
var licenseElement = infoElement.XPathSelectElement(Constants.Packaging.PackageLicenseXpath);
var authorNameElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorNameXpath);
var authorUrlElement = infoElement.XPathSelectElement(Constants.Packaging.AuthorWebsiteXpath);
var readmeElement = infoElement.XPathSelectElement(Constants.Packaging.ReadmeXpath);
XElement controlElement = xRootElement.Element(Constants.Packaging.ControlNodeName);
return new MetaData
{
Name = StringValue(nameElement),
Version = StringValue(versionElement),
Url = StringValue(urlElement),
License = StringValue(licenseElement),
LicenseUrl = StringAttribute(licenseElement, Constants.Packaging.PackageLicenseXpathUrlAttribute),
AuthorName = StringValue(authorNameElement),
AuthorUrl = StringValue(authorUrlElement),
Readme = StringValue(readmeElement),
Control = StringValue(controlElement),
ReqMajor = IntValue(majorElement),
ReqMinor = IntValue(minorElement),
ReqPatch = IntValue(patchElement),
};
}
private static string StringValue(XElement xElement, string defaultValue = "")
{
return xElement == null ? defaultValue : xElement.Value;
}
private static string StringAttribute(XElement xElement, string attribute, string defaultValue = "")
{
return xElement == null
? defaultValue
: xElement.HasAttributes ? xElement.AttributeValue<string>(attribute) : defaultValue;
}
private static int IntValue(XElement xElement, int defaultValue = 0)
{
int val;
return xElement == null ? defaultValue : int.TryParse(xElement.Value, out val) ? val : defaultValue;
}
private static string UpdatePathPlaceholders(string path)
{
if (path.Contains("[$"))
{
//this is experimental and undocumented...
path = path.Replace("[$UMBRACO]", SystemDirectories.Umbraco);
path = path.Replace("[$UMBRACOCLIENT]", SystemDirectories.UmbracoClient);
path = path.Replace("[$CONFIG]", SystemDirectories.Config);
path = path.Replace("[$DATA]", SystemDirectories.Data);
}
return path;
}
}
public class FileInPackageInfo : IFileInPackageInfo
{
public string RelativePath
{
get { return Path.Combine(RelativeDir, FileName); }
}
public string FileNameInPackage { get; set; }
public string RelativeDir { get; set; }
public string DestinationRootDir { private get; set; }
public string Directory
{
get { return Path.Combine(DestinationRootDir, RelativeDir); }
}
public string FullPath
{
get { return Path.Combine(DestinationRootDir, RelativePath); }
}
public string FileName { get; set; }
}
public interface IFileInPackageInfo
{
string RelativeDir { get; }
string RelativePath { get; }
string FileName { get; set; }
}
}