context: using System;
  using System.Configuration;
  using System.Diagnostics.CodeAnalysis;
  using System.IO;
  using System.Linq;
  using System.Text.RegularExpressions;
  using System.Threading;
  using System.Web;
  using System.Web.Configuration;
  using SmartStore.Collections;
  using SmartStore.Core.Data;
  using SmartStore.Core.Domain.Stores;
  using SmartStore.Core.Infrastructure;
  using SmartStore.Utilities;
  using System.Net;
  using System.Text;
  using System.Collections.Generic;
  using System.Collections.Concurrent;
  using System.Net.Sockets;
  
  namespace SmartStore.Core
  {
      public partial class WebHelper : IWebHelper
      {
  		private static object s_lock = new object();
  		private static bool? s_optimizedCompilationsEnabled;
  		private static AspNetHostingPermissionLevel? s_trustLevel;
  		private static readonly Regex s_staticExts = new Regex(@"(.*?)\.(css|js|png|jpg|jpeg|gif|bmp|html|htm|xml|pdf|doc|xls|rar|zip|ico|eot|svg|ttf|woff|otf|axd|ashx|less)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
  		private static readonly Regex s_htmlPathPattern = new Regex(@"(?<=(?:href|src)=(?:""|'))(?!https?://)(?<url>[^(?:""|')]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline);
  		private static readonly Regex s_cssPathPattern = new Regex(@"url\('(?<url>.+)'\)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline);
  		private static ConcurrentDictionary<int, string> s_safeLocalHostNames = new ConcurrentDictionary<int, string>();
  
  		private readonly HttpContextBase _httpContext;
          private bool? _isCurrentConnectionSecured;
  		private string _storeHost;
  		private string _storeHostSsl;
  		private string _ipAddress;
  		private bool? _appPathPossiblyAppended;
  		private bool? _appPathPossiblyAppendedSsl;
  
  		private Store _currentStore;
  
          public WebHelper(HttpContextBase httpContext)
          {
              this._httpContext = httpContext;
          }
  
          public virtual string GetUrlReferrer()
          {
              string referrerUrl = string.Empty;
  
              if (_httpContext != null &&
                  _httpContext.Request != null &&
                  _httpContext.Request.UrlReferrer != null)
                  referrerUrl = _httpContext.Request.UrlReferrer.ToString();
  
              return referrerUrl;
          }
  
  		public virtual string GetClientIdent()
  		{
  			var ipAddress = this.GetCurrentIpAddress();
  			var userAgent = _httpContext.Request != null ? _httpContext.Request.UserAgent : string.Empty;
  
  			if (ipAddress.HasValue() && userAgent.HasValue())
  			{
  				return (ipAddress + userAgent).GetHashCode().ToString();
  			}
  
  			return null;
  		}
  
          public virtual string GetCurrentIpAddress()
          {
  			if (_ipAddress != null)
  			{
  				return _ipAddress;
  			}
  
  			if (_httpContext == null && _httpContext.Request == null)
  			{
  				return string.Empty;
  			}
  
  			var vars = _httpContext.Request.ServerVariables;
  
  			var keysToCheck = new string[] 
  			{
  				"HTTP_CLIENT_IP",
  				"HTTP_X_FORWARDED_FOR",
  				"HTTP_X_FORWARDED",
  				"HTTP_X_CLUSTER_CLIENT_IP",
  				"HTTP_FORWARDED_FOR",
  				"HTTP_FORWARDED",
  				"REMOTE_ADDR",
  				"HTTP_CF_CONNECTING_IP"
  			};
  
  			string result = null;
  
  			foreach (var key in keysToCheck)
  			{
  				var ipString = vars[key];
  				if (ipString.HasValue())
  				{
  					var arrStrings = ipString.Split(',');
  					// Take the last entry
  					ipString = arrStrings[arrStrings.Length - 1].Trim();
  
  					IPAddress address;
  					if (IPAddress.TryParse(ipString, out address))
  					{
  						result = ipString;
  						break;
  					}
  				}
  			}			
  
  			if (result == "::1")
  			{
  				result = "127.0.0.1";
  			}			
  
  			return (_ipAddress = result.EmptyNull());
          }
          
          public virtual string GetThisPageUrl(bool includeQueryString)
          {
              bool useSsl = IsCurrentConnectionSecured();
              return GetThisPageUrl(includeQueryString, useSsl);
          }
  
          public virtual string GetThisPageUrl(bool includeQueryString, bool useSsl)
          {
              string url = string.Empty;
              if (_httpContext == null || _httpContext.Request == null)
                  return url;
  
              if (includeQueryString)
              {
                  bool appPathPossiblyAppended;
                  string storeHost = GetStoreHost(useSsl, out appPathPossiblyAppended).TrimEnd('/');
  
                  string rawUrl;
                  if (appPathPossiblyAppended)
                  {
                      string temp = _httpContext.Request.AppRelativeCurrentExecutionFilePath.TrimStart('~');
                      rawUrl = temp;
                  }
                  else
                  {
                      rawUrl = _httpContext.Request.RawUrl;
                  }
                  
                  url = storeHost + rawUrl;
              }
              else
              {
  				if (_httpContext.Request.Url != null)
  				{
  					url = _httpContext.Request.Url.GetLeftPart(UriPartial.Path);
  				}
              }
  
              return url;
          }
  
          public virtual bool IsCurrentConnectionSecured()
          {
              if (!_isCurrentConnectionSecured.HasValue)
              {
                  _isCurrentConnectionSecured = false;
                  if (_httpContext != null && _httpContext.Request != null)
                  {
                      _isCurrentConnectionSecured = _httpContext.Request.IsSecureConnection();
                  }
              }
  
              return _isCurrentConnectionSecured.Value;
          }
          
          public virtual string ServerVariables(string name)
          {
              string result = string.Empty;
  
              try
              {
  				if (_httpContext != null && _httpContext.Request != null)
  				{
  					if (_httpContext.Request.ServerVariables[name] != null)
  					{
  						result = _httpContext.Request.ServerVariables[name];
  					}
  				}
              }
              catch
              {
                  result = string.Empty;
              }
              return result;
          }
  
  	    [SuppressMessage("ReSharper", "UnusedMember.Local")]
  	    private string GetHostPart(string url)
          {
              var uri = new Uri(url);
              var host = uri.GetComponents(UriComponents.Scheme | UriComponents.Host, UriFormat.Unescaped);
              return host;
          }
  
          private string GetStoreHost(bool useSsl, out bool appPathPossiblyAppended)
          {
  			string cached = useSsl ? _storeHostSsl : _storeHost;
  			if (cached != null)
  			{
  				appPathPossiblyAppended = useSsl ? _appPathPossiblyAppendedSsl.Value : _appPathPossiblyAppended.Value;
  				return cached;
  			}
  
  			appPathPossiblyAppended = false;
              var result = "";
              var httpHost = ServerVariables("HTTP_HOST");
  
              if (httpHost.HasValue())
              {
                  result = "http://" + httpHost.EnsureEndsWith("/");
              }
  
              if (!DataSettings.DatabaseIsInstalled())
              {
                  if (useSsl)
                  {
                      // Secure URL is not specified.
                      // So a store owner wants it to be detected automatically.
                      result = result.Replace("http:/", "https:/");
                  }
              }
              else
              {
  				//let's resolve IWorkContext  here.
  				//Do not inject it via contructor because it'll cause circular references
  
  				if (_currentStore == null)
  				{
  					IStoreContext storeContext;
  					if (EngineContext.Current.ContainerManager.TryResolve<IStoreContext>(null, out storeContext)) // Unit test safe!
  					{
  						_currentStore = storeContext.CurrentStore;
  						if (_currentStore == null)
  							throw new Exception("Current store cannot be loaded");
  					}
  				}
  
  				if (_currentStore != null)
  				{
  					var securityMode = _currentStore.GetSecurityMode();
  
  					if (httpHost.IsEmpty())
  					{
  						// HTTP_HOST variable is not available.
  						// It's possible only when HttpContext is not available (for example, running in a schedule task)
  						result = _currentStore.Url.EnsureEndsWith("/");
  
  						appPathPossiblyAppended = true;
  					}
  
  					if (useSsl)
  					{
  						if (securityMode == HttpSecurityMode.SharedSsl)
  						{
  							// Secure URL for shared ssl specified. 
  							// So a store owner doesn't want it to be resolved automatically.
  							// In this case let's use the specified secure URL
  							result = _currentStore.SecureUrl.EmptyNull();
  
  							if (!result.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
  							{
  								result = "https://" + result;
  							}
  
  							appPathPossiblyAppended = true;
  						}
  						else
  						{
  							// Secure URL is not specified.
  							// So a store owner wants it to be resolved automatically.
  							result = result.Replace("http:/", "https:/");
  						}
  					}
  					else // no ssl
  					{
  						if (securityMode == HttpSecurityMode.SharedSsl)
  						{
  							// SSL is enabled in this store and shared ssl URL is specified.
  							// So a store owner doesn't want it to be resolved automatically.
  							// In this case let's use the specified non-secure URL
  
  							result = _currentStore.Url;
  							appPathPossiblyAppended = true;
  						}
  					}
  				}
              }
  
  			// cache results for request
  			result = result.EnsureEndsWith("/");
  			if (useSsl)
  			{
  				_storeHostSsl = result;
  				_appPathPossiblyAppendedSsl = appPathPossiblyAppended;
  			}
  			else
  			{
  				_storeHost = result;
  				_appPathPossiblyAppended = appPathPossiblyAppended;
  			}
  
              return result;
          }
          
          public virtual string GetStoreLocation()
          {
              bool useSsl = IsCurrentConnectionSecured();
              return GetStoreLocation(useSsl);
          }
  
          public virtual string GetStoreLocation(bool useSsl)
          {
              //return HostingEnvironment.ApplicationVirtualPath;
  
              bool appPathPossiblyAppended;
              string result = GetStoreHost(useSsl, out appPathPossiblyAppended);
  
              if (result.EndsWith("/"))
              {
                  result = result.Substring(0, result.Length - 1);
              }
  
              if (_httpContext != null && _httpContext.Request != null)
              {
                  var appPath = _httpContext.Request.ApplicationPath;
                  if (!appPathPossiblyAppended && !result.EndsWith(appPath, StringComparison.OrdinalIgnoreCase))
                  {
                      // in a shared ssl scenario the user defined https url could contain
                      // the app path already. In this case we must not append.
                      result = result + appPath;
                  }           
              }
  
              if (!result.EndsWith("/"))
              {
                  result += "/";
              }
  
              return result;
          }
          
          public virtual bool IsStaticResource(HttpRequest request)
          {
  			return IsStaticResourceRequested(new HttpRequestWrapper(request));
          }
  
  		public static bool IsStaticResourceRequested(HttpRequest request)
  		{
  			Guard.ArgumentNotNull(() => request);
  			return s_staticExts.IsMatch(request.Path);
  		}
  
  		public static bool IsStaticResourceRequested(HttpRequestBase request)
  		{
  			// unit testable
  			Guard.ArgumentNotNull(() => request);
  			return s_staticExts.IsMatch(request.Path);
  		}
          
          public virtual string MapPath(string path)
          {
  			return CommonHelper.MapPath(path, false);
          }
          
          public virtual string ModifyQueryString(string url, string queryStringModification, string anchor)
          {
  			url = url.EmptyNull();
  			queryStringModification = queryStringModification.EmptyNull();
  
  			string curAnchor = null;
  
  			var hsIndex = url.LastIndexOf('#');
  			if (hsIndex >= 0)
  			{
  				curAnchor = url.Substring(hsIndex);
  				url = url.Substring(0, hsIndex);
  			}
  			
  			var parts = url.Split(new[] { '?' });
  			var current = new QueryString(parts.Length == 2 ? parts[1] : "");
  			var modify = new QueryString(queryStringModification);
  
  			foreach (var nv in modify.AllKeys)
  			{
  				current.Add(nv, modify[nv], true);
  			}
  
  			var result = string.Concat(
  				parts[0],
  				current.ToString(),
  				anchor.NullEmpty() == null ? (curAnchor == null ? "" : "#" + curAnchor) : "#" + anchor
  			);
  
  			return result;
          }
  
          public virtual string RemoveQueryString(string url, string queryString)
          {
  			var parts = url.SplitSafe("?");
  
  			var current = new QueryString(parts.Length == 2 ? parts[1] : "");
  
  			if (current.Count > 0 && queryString.HasValue())
  			{
  				current.Remove(queryString);
  			}
  
  			var result = string.Concat(parts[0], current.ToString());
  			return result;
          }
          
          public virtual T QueryString<T>(string name)
          {
              string queryParam = null;
  
              if (_httpContext != null && _httpContext.Request.QueryString[name] != null)
                  queryParam = _httpContext.Request.QueryString[name];
  
              if (!String.IsNullOrEmpty(queryParam))
                  return queryParam.Convert<T>();
  
              return default(T);
          }
          
          public virtual void RestartAppDomain(bool makeRedirect = false, string redirectUrl = "", bool aggressive = false)
          {
  			HttpRuntime.UnloadAppDomain();
  
  			if (aggressive)
  			{
  				TryWriteBinFolder();
  			}
  			else
  			{
  				// without this, MVC may fail resolving controllers for newly installed plugins after IIS restart
  				Thread.Sleep(250);
  			}
  
              // If setting up plugins requires an AppDomain restart, it's very unlikely the
              // current request can be processed correctly.  So, we redirect to the same URL, so that the
              // new request will come to the newly started AppDomain.
              if (_httpContext != null && makeRedirect)
              {
  				if (_httpContext.Request.RequestType == "GET")
  				{
  					if (string.IsNullOrEmpty(redirectUrl))
  					{
  						redirectUrl = GetThisPageUrl(true);
  					}
  					_httpContext.Response.Redirect(redirectUrl, true /*endResponse*/);
  				}
  				else
  				{
  					// Don't redirect posts...
  					_httpContext.Response.ContentType = "text/html";
  					_httpContext.Response.WriteFile("~/refresh.html");
  					_httpContext.Response.End();
  				}
              }
          }
  
  	    [SuppressMessage("ReSharper", "UnusedMember.Local")]
  	    private bool TryWriteWebConfig()
          {
              try
              {
                  // In medium trust, "UnloadAppDomain" is not supported. Touch web.config
                  // to force an AppDomain restart.
                  File.SetLastWriteTimeUtc(MapPath("~/web.config"), DateTime.UtcNow);
                  return true;
              }
              catch
              {
                  return false;
              }
          }
  
  	    [SuppressMessage("ReSharper", "UnusedMember.Local")]
  	    private bool TryWriteGlobalAsax()
          {
              try
              {
                  //When a new plugin is dropped in the Plugins folder and is installed into SmartSTore.NET, 
                  //even if the plugin has registered routes for its controllers, 
                  //these routes will not be working as the MVC framework can't
                  //find the new controller types in order to instantiate the requested controller. 
                  //That's why you get these nasty errors 
                  //i.e "Controller does not implement IController".
                  //The solution is to touch the 'top-level' global.asax file
                  File.SetLastWriteTimeUtc(MapPath("~/global.asax"), DateTime.UtcNow);
                  return true;
              }
              catch
              {
                  return false;
              }
          }
  
  		private bool TryWriteBinFolder()
  		{
  			try
  			{
  
gt: 				var binMarker = MapPath("~/bin/HostRestart");