
//============================================================================================================
// Microsoft Updater Application Block for .NET
//  http://msdn.microsoft.com/library/en-us/dnbda/html/updater.asp
//	
// ManifestSettings.cs
// 
//============================================================================================================
// Copyright (C) 2000-2001 Microsoft Corporation
// All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR
// FITNESS FOR A PARTICULAR PURPOSE.
//============================================================================================================


using System;
using System.Collections;
using System.Xml;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting;
using Microsoft.ApplicationBlocks.ApplicationUpdater.Interfaces;

namespace ManifestUtility
{
	/// <summary>
	/// Summary description for ManifestSettings.
	/// </summary>
	public  class ManifestSettings
	{
		#region delarations

		private  string _folderPath = "";
		private string _updateLocation = "";
		private string _version = "";
		private string _key = "";
		private bool _usePostProcessor = false;
		private string _postProcessorAssembly = "";
		private string _postProcessorClass = "";
		private string _validatorAssembly = "";
		private string _validatorClass = "";

		private ArrayList filesForUpdate;
		private IValidator validator;

		#endregion

		#region constructor

		public ManifestSettings()
		{
		}

		#endregion

		#region properties

		public  string FolderPath
		{
			get
			{
				return _folderPath;
			}
			set
  			{
				_folderPath = value;
			}
		}
		public  string UpdateLocation
		{
			get
			{
				return _updateLocation;
			}
			set
			{
				_updateLocation = value;
			}
		}

		public  string Version
		{
			get
			{
				return _version;
			}
			set
			{
				_version = value;
			}
		}
		public  string Key
		{
			get
			{
				return _key;
			}
			set
			{
				_key = value;
			}
		}
		public bool UsePostProcessor
		{
			get
			{
				return _usePostProcessor;
			}
			set
			{
				_usePostProcessor = value;
			}
		}
		public string PostProcessorAssembly
		{
			get
			{
				return _postProcessorAssembly;
			}
			set
			{
				_postProcessorAssembly = value;
			}
		}
		public string PostProcessorClass
		{
			get
			{
				return _postProcessorClass;
			}
			set
			{
				_postProcessorClass = value;
			}
		}
		public string ValidatorAssembly
		{
			get
			{
				return _validatorAssembly;
			}
			set
			{
				_validatorAssembly = value;
			}
		}
		public string ValidatorClass
		{
			get
			{
				return _validatorClass;
			}
			set
			{
				_validatorClass = value;
			}
		}
		#endregion
		
		public XmlDocument Create()
		{
			XmlDocument xmlDoc = new XmlDocument();
			XmlNode node = null;
			XmlNode filesNode = null;
			XmlNode fileNode = null;
			XmlAttribute attribute = null;
			string signature = "";

			// Instantiate the validator
			ObjectHandle handle = Activator.CreateInstanceFrom( this.ValidatorAssembly, this.ValidatorClass );
			object instance = handle.Unwrap();
			validator = ( IValidator )instance;

			//  get all files first
			PutFilesInCollection();

			//  now actually generate xml
			try
			{
				//  tell xml to keep white space
				xmlDoc.PreserveWhitespace = true;

			#region Create Main ServerManifest Xml Nodes

				xmlDoc.AppendChild( xmlDoc.CreateElement( "ServerApplicationInfo" ) );

				//  create/set available version
				node = xmlDoc.SelectSingleNode( "ServerApplicationInfo" ).AppendChild( xmlDoc.CreateElement( "availableVersion" ) );
				node.InnerText = this.Version;

				//  create/set update URL root
				node = xmlDoc.SelectSingleNode( "ServerApplicationInfo" ).AppendChild( xmlDoc.CreateElement( "updateLocation" ) );
				node.InnerText = this.UpdateLocation;

				//  create the files node, which will have individual file nodes added later
				xmlDoc.SelectSingleNode( "ServerApplicationInfo" ).AppendChild( xmlDoc.CreateElement( "files" ) );

				#endregion
                			
				//  select the "files" node for quick appending in following loop:
				filesNode = xmlDoc.SelectSingleNode( "ServerApplicationInfo/files" );
				foreach ( FileInfo fInfo in filesForUpdate )
				{
				
					#region Append File Node; Add Attributes and their Values

					//  append a new file node to "files" node, get reference to that new file node
					fileNode = filesNode.AppendChild( xmlDoc.CreateElement( "file" ) );

					//  using new file node, append attributes
					attribute = fileNode.Attributes.Append( xmlDoc.CreateAttribute( "name" ) );
					
					//  Updater will require relative paths to root to reconstruct dir structure at destination
					//  manipulate our known Root dir and current filename to derive relative path;
					//  use Utility to remove initial slash, because 
					//  OUR CONVENTION IS ALL ROOT PATHS TERMINATE WITH SLASH, ALL RELATIVE PATHS DO NOT INITIATE WITH SLASH
					string relPath = ExtractRelativePath( fInfo.FullName, this.FolderPath );

					// make the slashes go the right way
					int lastFwdSlash = this.UpdateLocation.LastIndexOf("/");
					int lastBckSlash = this.UpdateLocation.LastIndexOf(@"\@");
					if ( lastFwdSlash > lastBckSlash)
					{
							relPath = relPath.Replace(@"\", "/");
					}
					attribute.Value = relPath;

					//  using new file node, append attributes
					attribute = fileNode.Attributes.Append( xmlDoc.CreateAttribute( "signature" ) );
					//  use Validation utility to get signature:
					//  CHECK, if validator null then signing not desired
					if( null != validator )
					{
						signature = validator.Sign( fInfo.FullName, this.Key );
					}
					else
					{
						signature = "";
					}
					//  set signature attribute value
					attribute.Value = signature;



					#endregion

				}  //  FOREACH

				#region Add Post-Processing Assembly Information

				//  skip post-processor if told to omit, or if no object instance available
				if( ( this.UsePostProcessor ) && ( null != this.PostProcessorClass ) )
				{
					CreatePostProcessingAttribute( xmlDoc );
				}

				#endregion

				#region ****  SIGN MANIFEST  **** : Add Keyed Hash "Signature" to Root Element

				SignManifest( xmlDoc );

				return xmlDoc;

				#endregion

	

			}
			catch( Exception e )
			{
				throw e;
			}
			
		}
		
		
		private void CreatePostProcessingAttribute( XmlDocument xmlDoc  )
		{
			XmlAttribute attribute = null;
			XmlNode node = null;

			//  create the node for IPP
			//  check if it exists first
			node = xmlDoc.SelectSingleNode( "ServerApplicationInfo/postProcessor" );
			if( null == node )
			{
				node = xmlDoc.SelectSingleNode( "ServerApplicationInfo" ).AppendChild( xmlDoc.CreateElement( "postProcessor" ) );
			}

			//  find and load post-processing assembly
			//  ALWAYS it implements IPostProcessor which presents known "void Run()" to AppUpdater
			//DESIGN:  we always choose the FIRST instance of IPostProcessor we found...
			try
			{

				//  the type attr
				attribute = node.Attributes.Append( xmlDoc.CreateAttribute( "type" ) );
				attribute.Value = this.PostProcessorClass;
				//  the assembly attr
				attribute = node.Attributes.Append( xmlDoc.CreateAttribute( "assembly" ) );
				Assembly asm = Assembly.LoadFrom(this.PostProcessorAssembly);
				attribute.Value = asm.FullName;
				//  the filename attr 
				attribute = node.Attributes.Append( xmlDoc.CreateAttribute( "name" ) );
				//  use Utility function to get the UN-pre-slashed relative path to this assembly
				attribute.Value = ExtractRelativePath( this.PostProcessorAssembly, this.FolderPath );
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		
		
		private void SignManifest( XmlDocument xmlDoc  )
		{
			string signature = "";
			XmlAttribute attribute = null;

			#region Add Keyed Hash "Signature" to Root Element

			//  check if signature attribute exists already
			attribute = (XmlAttribute)xmlDoc.SelectSingleNode( "ServerApplicationInfo/@signature" );
			if( null == attribute )
			{
				//  create the MANIFEST's signature attribute--we pass the contents of the ServerApplicationInfo node
				//  to validator to generate a keyed hash of everything inside this root node; client then validates
				//  server manifest
				attribute = xmlDoc.SelectSingleNode( "ServerApplicationInfo" ).Attributes.Append( xmlDoc.CreateAttribute( "signature" ) );
			}

			//  pass contents of root node to validator for "signing" with keyed hash;
			//  IF validator is null, don't sign
			if( null != validator )
			{
				signature = validator.Sign
					( 
					xmlDoc.SelectSingleNode( "ServerApplicationInfo" ), 
					this.Key
					);
			}

			//  put hash signature value in signature attribute obtained above
			attribute.Value = signature ;

			#endregion
		}
		
		
		private  void PutFilesInCollection()
		{
			//  check to be sure _folderpath is valid first
			if ( Directory.Exists( this.FolderPath ) )
			{
				//  set files member to new arraylist
				filesForUpdate = new ArrayList();

				//  using member _folderpath, populate member MakerState.filesForUpdate with all files in target directory
				filesForUpdate = GetAllFiles( this.FolderPath );			
			}
		}

	
		/// <summary>
		/// Recursive function that walks given folder path and adds all files to the MakerState.filesForUpdate collection
		/// </summary>
		/// <param name="folderPath">the root directory to start with; function is recursive so will walk subdirs too</param>
		private  ArrayList GetAllFiles( string folderPath )
		{
			DirectoryInfo dirInfo = new DirectoryInfo( folderPath );

			foreach( FileSystemInfo fsi in dirInfo.GetFileSystemInfos() )
			{
				if ( fsi is FileInfo )
				{
					filesForUpdate.Add( fsi );					
				}
				else
				{
					//  it's not a file must be a folder; recurse rinse repeat fold manipulate
					GetAllFiles( fsi.FullName );
				}
			}

			return filesForUpdate;
			
		}

		private string ExtractRelativePath( string fullPath, string rootPath )
		{
			string relativeFilePath = "";

			relativeFilePath = fullPath.Substring( rootPath.Length, ( fullPath.Length - rootPath.Length ) );
			relativeFilePath = RemoveInitialSlash( relativeFilePath );
			
			return relativeFilePath;
		}

		private string RemoveInitialSlash( string path )
		{
			string outPath = path;

			if( path.IndexOf( @"\" ) == 0 )
			{
				outPath = path.Substring( 1, ( path.Length - 1 ) );
			}
			
			if( path.IndexOf( "/" ) == 0 )
			{
				outPath = path.Substring( 1, ( path.Length - 1 ) );
			}

			return outPath;

		}
	}
}
