//
// System.Web.Configuration.WebConfigurationHost.cs
//
// Authors:
//  Lluis Sanchez Gual (lluis@novell.com)
//  Marek Habersack <mhabersack@novell.com>
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (C) 2005-2009 Novell, Inc (http://www.novell.com)
//


using System;
using System.Collections;
using System.IO;
using System.Security;
using System.Configuration;
using System.Configuration.Internal;
using System.Web.Hosting;
using System.Web.Util;
using System.Reflection;

/*
 * this class needs to be rewritten to support usage of the
 * IRemoteWebConfigurationHostServer interface.  Once that's done, we
 * need an implementation of that interface that talks (through a web
 * service?) to a remote site..
 *
 * for now, though, just implement it as we do
 * System.Configuration.InternalConfigurationHost, i.e. the local
 * case.
 */
namespace System.Web.Configuration
{
	class WebConfigurationHost: IInternalConfigHost
	{
		WebConfigurationFileMap map;
		const string MachinePath = ":machine:";
		const string MachineWebPath = ":web:";

		string appVirtualPath;
		
		public virtual object CreateConfigurationContext (string configPath, string locationSubPath)
		{
			return new WebContext (WebApplicationLevel.AtApplication /* XXX */,
					       "" /* site XXX */,
					       "" /* application path XXX */,
					       configPath,
					       locationSubPath);
		}
		
		public virtual object CreateDeprecatedConfigContext (string configPath)
		{
			return new HttpConfigurationContext(configPath);
		}
		
		public virtual string DecryptSection (string encryptedXml, ProtectedConfigurationProvider protectionProvider, ProtectedConfigurationSection protectedSection)
		{
			if (protectedSection == null)
				throw new ArgumentNullException ("protectedSection");

			return protectedSection.EncryptSection (encryptedXml, protectionProvider);
		}
		
		public virtual void DeleteStream (string streamName)
		{
			File.Delete (streamName);
		}
		
		public virtual string EncryptSection (string clearXml, ProtectedConfigurationProvider protectionProvider, ProtectedConfigurationSection protectedSection)
		{
			if (protectedSection == null)
				throw new ArgumentNullException ("protectedSection");

			return protectedSection.EncryptSection (clearXml, protectionProvider);
		}
		
		public virtual string GetConfigPathFromLocationSubPath (string configPath, string locationSubPath)
		{
			if (!String.IsNullOrEmpty (locationSubPath) && !String.IsNullOrEmpty (configPath)) {
				string relConfigPath = configPath.Length == 1 ? null : configPath.Substring (1) + "/";
				if (relConfigPath != null && locationSubPath.StartsWith (relConfigPath, StringComparison.Ordinal))
					locationSubPath = locationSubPath.Substring (relConfigPath.Length);
			}
			
			string ret = configPath + "/" + locationSubPath;
			if (!String.IsNullOrEmpty (ret) && ret [0] == '/')
				return ret.Substring (1);
			
			return ret;
		}
		
		public virtual Type GetConfigType (string typeName, bool throwOnError)
		{
		        Type type = HttpApplication.LoadType (typeName);
			if (type == null && throwOnError)
				throw new ConfigurationErrorsException ("Type not found: '" + typeName + "'");
			return type;
		}
		
		public virtual string GetConfigTypeName (Type t)
		{
			return t.AssemblyQualifiedName;
		}
		
		public virtual void GetRestrictedPermissions (IInternalConfigRecord configRecord, out PermissionSet permissionSet,
							      out bool isHostReady)
		{
			throw new NotImplementedException ();
		}
		
		public virtual string GetStreamName (string configPath)
		{
			if (configPath == MachinePath) {
				if (map == null)
					return System.Runtime.InteropServices.RuntimeEnvironment.SystemConfigurationFile;
				else
					return map.MachineConfigFilename;
			} else if (configPath == MachineWebPath) {
				string mdir;

				if (map == null)
					mdir = Path.GetDirectoryName (System.Runtime.InteropServices.RuntimeEnvironment.SystemConfigurationFile);
				else
					mdir = Path.GetDirectoryName (map.MachineConfigFilename);

				return GetWebConfigFileName (mdir);
			}
			
			string dir = MapPath (configPath);
			return GetWebConfigFileName (dir);
		}
		
		public virtual string GetStreamNameForConfigSource (string streamName, string configSource)
		{
			throw new NotImplementedException ();
		}
		
		public virtual object GetStreamVersion (string streamName)
		{
			throw new NotImplementedException ();
		}
		
		public virtual IDisposable Impersonate ()
		{
			throw new NotImplementedException ();
		}
		
		public virtual void Init (IInternalConfigRoot root, params object[] hostInitParams)
		{
		}
		
		public virtual void InitForConfiguration (ref string locationSubPath, out string configPath,
							  out string locationConfigPath, IInternalConfigRoot root,
							  params object[] hostInitConfigurationParams)
		{
			string fullPath = (string) hostInitConfigurationParams [1];
			map = (WebConfigurationFileMap) hostInitConfigurationParams [0];
			bool inAnotherApp = false;

			if ((hostInitConfigurationParams.Length > 7)
				&& (hostInitConfigurationParams[7] is bool))
				inAnotherApp = (bool) hostInitConfigurationParams[7];

			if (inAnotherApp)
				appVirtualPath = fullPath;
			else
				appVirtualPath = HttpRuntime.AppDomainAppVirtualPath;
			
			if (locationSubPath == MachineWebPath) {
				locationSubPath = MachinePath;
				configPath = MachineWebPath;
				locationConfigPath = null;
			} else if (locationSubPath == MachinePath) {
				locationSubPath = null;
				configPath = MachinePath;
				locationConfigPath = null;
			} else {
				int i;
				if (locationSubPath == null) {
					configPath = fullPath;
					if (configPath.Length > 1)
						configPath = VirtualPathUtility.RemoveTrailingSlash (configPath);
				} else
					configPath = locationSubPath;
				
				if (configPath == HttpRuntime.AppDomainAppVirtualPath || configPath == "/")
					i = -1;
				else
					i = configPath.LastIndexOf ("/");

				if (i != -1) {
					locationConfigPath = configPath.Substring (i+1);
					
					if (i == 0)
						locationSubPath = "/";
					else
						locationSubPath = fullPath.Substring (0, i);
				} else {
					locationSubPath = MachineWebPath;
					locationConfigPath = null;
				}
			}
		}
		
		public string MapPath (string virtualPath)
		{
			if (!String.IsNullOrEmpty (virtualPath)) {
				if (virtualPath.StartsWith (System.Web.Compilation.BuildManager.FAKE_VIRTUAL_PATH_PREFIX, StringComparison.Ordinal))
					return HttpRuntime.AppDomainAppPath;
			}
			
			if (map != null)
				return MapPathFromMapper (virtualPath);
			else if (HttpContext.Current != null && HttpContext.Current.Request != null)
				return HttpContext.Current.Request.MapPath (virtualPath);
			else if (HttpRuntime.AppDomainAppVirtualPath != null &&
				 virtualPath.StartsWith (HttpRuntime.AppDomainAppVirtualPath)) {
				if (virtualPath == HttpRuntime.AppDomainAppVirtualPath)
					return HttpRuntime.AppDomainAppPath;
				return UrlUtils.Combine (HttpRuntime.AppDomainAppPath,
							 virtualPath.Substring (HttpRuntime.AppDomainAppVirtualPath.Length));
			}
			
			return virtualPath;
		}
		
		public string NormalizeVirtualPath (string virtualPath)
		{
			if (virtualPath == null || virtualPath.Length == 0)
				virtualPath = ".";
			else
				virtualPath = virtualPath.Trim ();

			if (virtualPath [0] == '~' && virtualPath.Length > 2 && virtualPath [1] == '/')
				virtualPath = virtualPath.Substring (1);
				
			if (System.IO.Path.DirectorySeparatorChar != '/')
				virtualPath = virtualPath.Replace (System.IO.Path.DirectorySeparatorChar, '/');

			if (UrlUtils.IsRooted (virtualPath)) {
				virtualPath = UrlUtils.Canonic (virtualPath);
			} else {
				if (map.VirtualDirectories.Count > 0) {
					string root = map.VirtualDirectories [0].VirtualDirectory;
					virtualPath = UrlUtils.Combine (root, virtualPath);
					virtualPath = UrlUtils.Canonic (virtualPath);
				}
			}
			return virtualPath;
		}

		public string MapPathFromMapper (string virtualPath)
		{
			string path = NormalizeVirtualPath (virtualPath);
			
			foreach (VirtualDirectoryMapping mapping in map.VirtualDirectories) {
				if (path.StartsWith (mapping.VirtualDirectory)) {
					int i = mapping.VirtualDirectory.Length;
					if (path.Length == i) {
						return mapping.PhysicalDirectory;
					}
					else if (path [i] == '/') {
						string pathPart = path.Substring (i + 1).Replace ('/', Path.DirectorySeparatorChar);
						return Path.Combine (mapping.PhysicalDirectory, pathPart);
					}
				}
			}
			throw new HttpException ("Invalid virtual directory: " + virtualPath);
		}

		internal static string GetWebConfigFileName (string dir)
		{
			AppDomain domain = AppDomain.CurrentDomain;
			bool hosted = (domain.GetData (ApplicationHost.MonoHostedDataKey) as string) == "yes";

			if (hosted)
				return ApplicationHost.FindWebConfig (dir);
			else {
				Assembly asm = Assembly.GetEntryAssembly () ?? Assembly.GetCallingAssembly ();
				string name = Path.GetFileName (asm.Location);
				string[] fileNames = new string[] {name + ".config", name + ".Config"};
				string appDir = domain.BaseDirectory;
				string file;

				foreach (string fn in fileNames) {
					file = Path.Combine (appDir, fn);
					if (File.Exists (file))
						return file;
				}
			}
			return null;
		}
		public virtual bool IsAboveApplication (string configPath)
		{
			return !configPath.Contains (HttpRuntime.AppDomainAppPath);
		}
		
		public virtual bool IsConfigRecordRequired (string configPath)
		{
			throw new NotImplementedException ();
		}
		
		public virtual bool IsDefinitionAllowed (string configPath, ConfigurationAllowDefinition allowDefinition,
							 ConfigurationAllowExeDefinition allowExeDefinition)
		{
			switch (allowDefinition) {
				case ConfigurationAllowDefinition.MachineOnly:
					return configPath == MachinePath || configPath == MachineWebPath;
				case ConfigurationAllowDefinition.MachineToWebRoot:
				case ConfigurationAllowDefinition.MachineToApplication:
					if (String.IsNullOrEmpty (configPath))
						return true;
					string normalized;

					if (VirtualPathUtility.IsRooted (configPath))
						normalized = VirtualPathUtility.Normalize (configPath);
					else
						normalized = configPath;
					
					if ((String.Compare (normalized, MachinePath, StringComparison.Ordinal) == 0) ||
						(String.Compare (normalized, MachineWebPath, StringComparison.Ordinal) == 0))
							return true;
				
					if ((String.Compare (normalized, appVirtualPath) != 0))
						return IsApplication (normalized);
				
					return true;
				default:
					return true;
			}
		}
		
		[MonoTODO("Should return false in case strPath points to the root of an application.")]
		internal bool IsApplication(string strPath)
		{
			return true;
		}
		
		public virtual bool IsFile (string streamName)
		{
			throw new NotImplementedException ();
		}
		
		public virtual bool IsLocationApplicable (string configPath)
		{
			throw new NotImplementedException ();
		}
		
		public virtual Stream OpenStreamForRead (string streamName)
		{
			if (!File.Exists (streamName)) {
				return null;
			}
				
			return new FileStream (streamName, FileMode.Open, FileAccess.Read);
		}

		[MonoTODO ("Not implemented")]
		public virtual Stream OpenStreamForRead (string streamName, bool assertPermissions)
		{
			throw new NotImplementedException ();
		}

		public virtual Stream OpenStreamForWrite (string streamName, string templateStreamName, ref object writeContext)
		{
			if (!IsAboveApplication (streamName))
				WebConfigurationManager.SuppressAppReload (true);

			return new FileStream (streamName, FileMode.Create, FileAccess.Write);
		}

		[MonoTODO ("Not implemented")]
		public virtual Stream OpenStreamForWrite (string streamName, string templateStreamName, ref object writeContext,
							  bool assertPermissions)
		{
			throw new NotImplementedException ();
		}
		
		public virtual bool PrefetchAll (string configPath, string streamName)
		{
			throw new NotImplementedException ();
		}
		
		public virtual bool PrefetchSection (string sectionGroupName, string sectionName)
		{
			throw new NotImplementedException ();
		}

		[MonoTODO ("Not implemented")]
		public virtual void RequireCompleteInit (IInternalConfigRecord configRecord)
		{
			throw new NotImplementedException ();
		}

		public virtual object StartMonitoringStreamForChanges (string streamName, StreamChangeCallback callback)
		{			
			throw new NotImplementedException ();
		}
		
		public virtual void StopMonitoringStreamForChanges (string streamName, StreamChangeCallback callback)
		{
			throw new NotImplementedException ();
		}
		
		public virtual void VerifyDefinitionAllowed (string configPath, ConfigurationAllowDefinition allowDefinition,
							     ConfigurationAllowExeDefinition allowExeDefinition,
							     IConfigErrorInfo errorInfo)
		{
			if (!IsDefinitionAllowed (configPath, allowDefinition, allowExeDefinition))
				throw new ConfigurationErrorsException ("The section can't be defined in this file (the allowed definition context is '" + allowDefinition + "').", errorInfo.Filename, errorInfo.LineNumber);
		}
		
		public virtual void WriteCompleted (string streamName, bool success, object writeContext)
		{
			WriteCompleted (streamName, success, writeContext, false);
		}		

		public virtual void WriteCompleted (string streamName, bool success, object writeContext, bool assertPermissions)
		{
			// There are probably other things to be done here, but for the moment we
			// just mark the completed write as one that should not cause application
			// reload. Note that it might already be too late for suppression, since the
			// FileSystemWatcher monitor might have already delivered the
			// notification. If the stream has been open using OpenStreamForWrite then
			// we're safe, though.

			if (!IsAboveApplication (streamName))
				WebConfigurationManager.SuppressAppReload (true);
		}

		public virtual bool SupportsChangeNotifications {
			get { return false; }
		}
		
		public virtual bool SupportsLocation {
			get { return false; }
		}
		
		public virtual bool SupportsPath {
			get { return false; }
		}
		
		public virtual bool SupportsRefresh {
			get { return false; }
		}

		[MonoTODO("Always returns false")]
		public virtual bool IsRemote {
			get { return false; }
		}

		[MonoTODO ("Not implemented")]
		public virtual bool IsFullTrustSectionWithoutAptcaAllowed (IInternalConfigRecord configRecord)
		{
			throw new NotImplementedException ();
		}

		[MonoTODO ("Not implemented")]
		public virtual bool IsInitDelayed (IInternalConfigRecord configRecord)
		{
			throw new NotImplementedException ();
		}

		[MonoTODO ("Not implemented")]
		public virtual bool IsSecondaryRoot (string configPath)
		{
			throw new NotImplementedException ();
		}

		[MonoTODO ("Not implemented")]
		public virtual bool IsTrustedConfigPath (string configPath)
		{
			throw new NotImplementedException ();
		}
	}
}