File: WebForm\General\ChartHttpHandler.cs
Project: ndp\fx\src\DataVisualization\System.Web.DataVisualization.csproj (System.Web.DataVisualization)

//--------------------------------------------------------------------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
//   Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//--------------------------------------------------------------------------------------------------------------------------
// @owner=alexgor, deliant
//==========================================================================================================================
//  File:		ChartHttpHandler.cs
//
//  Namespace:	Microsoft.Reporting.Chart.WebForms
//
//	Classes:	ChartHttpHandler
//
//  Purpose:	ChartHttpHandler is a static class which is responsible to handle with 
//              chart images, interactive images, scripts and other resources.
//              
//              
//	Reviewed:	DT
//	Reviewed:	deliant on 4/14/2011 			
//              MSRC#10470, VSTS#941768 http://vstfdevdiv:8080/web/wi.aspx?id=941768
//              Please review information associated with MSRC#10470 before making any changes to this file.
//              - Fixes:
//                  - Fixed Directory Traversal/Arbitrary File Read, Delete with malformed image key.
//                  - Honor HttpContext.Current.Trace.IsEnabled when generate and deliver chart trace info.
//                  - Handle empty guid parameter ("?g=") as invalid when enforcing privacy.
//                  - Replaced the privacy byte array comparison with custom check (otherwise posible EOS marker can return 0 length string).
//                  - Added fixed string to session key to avoid direct session access.
//
//   Added:  deliant on 4/48/2011 fix for VSTS: 3593 - ASP.Net chart under web farm exhibit fast performace degradation
//           Summary: Under large web farm setup ( ~16 processes and up) chart control image handler 
//                    soon starts to show performace degradation up to denial of service, when a file system is used as storage.
//            Issues: 
//                  - The image files in count over 2000 in one single folder causes exponentially growing slow response, 
//                    especially on the remote server. The fix places the Image files  in separate subfolders for each process. 
//                  - Private protection seeks and read several times in the image file istead reading the image at once 
//                    and then check for privacy marker.  Separate small network reads are expensive.
//                  - Due missing lock in initialization stage the chart lock files number can grow more that process max 
//                    number which can create abandon chart image files
//==========================================================================================================================
 
#region Namespaces
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.UI;
using System.IO;
using System.Web.Caching;
using System.Collections;
using System.Web.Configuration;
using System.Resources;
using System.Reflection;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Web.Hosting;
using System.Web.SessionState;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Globalization;
using System.Diagnostics.CodeAnalysis;
using System.Security.Permissions;
using System.Security;
using System.Security.Cryptography;
using System.Collections.ObjectModel;
using System.Web.UI.WebControls;
 
#endregion //Namespaces
 
namespace System.Web.UI.DataVisualization.Charting
{
    /// <summary>
    /// ChartHttpHandler processes HTTP Web requests using, handles chart images, scripts and other resources.
    /// </summary>
#if ASPPERM_35
	[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
#endif
    public class ChartHttpHandler : Page, IRequiresSessionState, IHttpHandler
    {
 
        #region Fields
 
        // flag that indicates whether this chart handler is installed
        private static bool _installed = false;
        
        // flag that indicates whether this chart handler is installed
        private static bool _installChecked = false;
 
        // storage settings
        private static ChartHttpHandlerSettings _parameters = null;
        
 
        // machine hash key which is part in chart image file name
        private static string _machineHash = "_" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + "_";
 
        // web gadren controller file. stays locked diring process lifetime.
		private static FileStream _controllerFileStream = null;
		private static string _controllerDirectory = null;
        private static object _initHandlerLock = new object();
        // used for storing Guid key in context;
        internal static string ContextGuidKey = "{89FA5660-BD13-4f1b-8C7C-355CEC92CC7E}";
 
        // web gadren controller file. stays locked diring process lifetime.
        private const string handlerCheckQry = "check";
 
        #endregion //Fields
 
        #region Consts
        
        internal const string ChartHttpHandlerName = "ChartImg.axd";
        internal const string ChartHttpHandlerAppSection = "ChartImageHandler";
        internal const string DefaultConfigSettings = @"storage=file;timeout=20;dir=c:\TempImageFiles\;";
        internal const string WebDevServerUseConfigSettings = "WebDevServerUseConfigSettings";
        #endregion //Consts
 
        #region Constructors
 
        /// <summary>
        /// Ensures that the handler is initialized.
        /// </summary>
        /// <param name="hardCheck">if set to <c>true</c> then will be thrown all excepitons.</param>
        private static void EnsureInitialized(bool hardCheck)
        {
            if (_installChecked)
            {
                return;
            }
            lock (_initHandlerLock)
            {
                if (_installChecked)
                {
                    return;
                }
                if (HttpContext.Current != null)
                {
                    try
                    {
                        using (TextWriter w = new StringWriter(CultureInfo.InvariantCulture))
                        {
                            HttpContext.Current.Server.Execute(ChartHttpHandlerName + "?" + handlerCheckQry + "=0", w);
                        }
                        _installed = true;
                    }
                    catch (HttpException)
                    {
                        if (hardCheck) throw;
                    }
                    catch (SecurityException)
                    {
                        // under minimal configuration we assume that the hanlder is installed if app settings are present.
                        _installed = !String.IsNullOrEmpty(WebConfigurationManager.AppSettings[ChartHttpHandlerAppSection]);
                    }
                }
                if (_installed || hardCheck)
                {
                    InitializeControllerFile();
                }
                _installChecked = true;
            }
        }
        
        /// <summary>
        /// Initializes the storage settings
        /// </summary>
        //static ChartHttpHandler()
        private static ChartHttpHandlerSettings InitializeParameters()
        {
 
            ChartHttpHandlerSettings result = new ChartHttpHandlerSettings();
            if (HttpContext.Current != null)
            {
                // Read settings from config; use DefaultConfigSettings in case when setting is not found
                string configSettings = WebConfigurationManager.AppSettings[ChartHttpHandlerAppSection];
                if (String.IsNullOrEmpty(configSettings))
                    configSettings = DefaultConfigSettings;
                
                result = new ChartHttpHandlerSettings(configSettings);
            }
            else
            {
                result.PrepareDesignTime();
            }
 
            return result;
        }
 
        private static void ResetControllerStream()
        {
            if (_controllerFileStream != null)
                _controllerFileStream.Dispose();
            _controllerFileStream = null;
            _controllerDirectory = null;
        }
 
        private static void InitializeControllerFile()
        {
            if (Settings.StorageType == ChartHttpHandlerStorageType.File && _controllerFileStream == null)
            {
                byte[] data = System.Text.Encoding.UTF8.GetBytes("chart io controller file");
                // 2048 processes max.
                for (Int32 i = 0; i < 2048; i++)
                {
                    try
                    {
                        ResetControllerStream();
                        string controllerFileName = String.Format(CultureInfo.InvariantCulture, "{0}msc_cntr_{1}.txt", Settings.Directory, i);
                        _controllerDirectory = String.Format(CultureInfo.InvariantCulture, "charts_{0}", i);
                        _controllerFileStream = new System.IO.FileStream(controllerFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
                        _controllerFileStream.Lock(0, data.Length);
                        _controllerFileStream.Write(data, 0, data.Length);
                        _machineHash = "_" + i + "_";
                        if (!Directory.Exists(Settings.Directory + _controllerDirectory))
                        {
                            Directory.CreateDirectory(Settings.Directory + _controllerDirectory);
                        }
                        else
                        {
                            TimeSpan lastWrite = DateTime.Now - Directory.GetLastWriteTime(Settings.Directory + _controllerDirectory);
                            if (lastWrite.Seconds < Settings.Timeout.Seconds)
                            {
                                continue;
                            }
                        }
                        return;
                    }
                    catch (IOException)
                    {
                        continue;
                    }
                    catch (Exception)
                    {
                        ResetControllerStream();
                        throw;
                    }
                }
                ResetControllerStream(); 
                throw new UnauthorizedAccessException(SR.ExceptionHttpHandlerTempDirectoryUnaccesible(Settings.Directory));
            }
        }
 
        #endregion //Constructors
 
        #region Methods
 
        #region ChartImage
 
        /// <summary>
        /// Processes the saved image.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <returns>false if the image cannot be processed</returns>
        private static bool ProcessSavedChartImage(HttpContext context)
        {
            // image delivery doesn't depend if handler is intitilzed or not.
            String key = context.Request["i"];
            CurrentGuidKey = context.Request["g"];
            IChartStorageHandler handler = GetHandler();
            try
            {
                Byte[] data = handler.Load(KeyToUnc(key));
                if (data != null && data.Length > 0)
                {
                    context.Response.Charset = "";
                    context.Response.ContentType = GetMime(key);
                    context.Response.BinaryWrite(data);
                    Diagnostics.TraceWrite(SR.DiagnosticChartImageServed(key), null);
                    if (Settings.StorageType == ChartHttpHandlerStorageType.Session || Settings.DeleteAfterServicing)
                    {
                        handler.Delete(key);
                        Diagnostics.TraceWrite(SR.DiagnosticChartImageDeleted(key), null);
                    }
                    return true;
                }
                if (!(handler is DefaultImageHandler))
                {
                    // the default handler will write more detailed message
                    Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailNotFound), null);
                }
            }
            catch (NullReferenceException nre)
            {
                Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, String.Empty), nre);
                throw;
            }
            catch (IOException ioe)
            {
                Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, String.Empty), ioe);
                throw;
            }
            catch (SecurityException se)
            {
                Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, String.Empty), se);
                throw;
            }
            return false;
        }
 
        #endregion //ChartImage
 
        #region Utilities
 
 
        /// <summary>
        /// Gets or sets the current GUID key.
        /// </summary>
        /// <value>The current GUID key.</value>
        internal static string CurrentGuidKey
        {
            get
            {
                if (HttpContext.Current != null)
                {
                    return (string)HttpContext.Current.Items[ContextGuidKey];
                }
                return String.Empty;
            }
            set
            {
                if (HttpContext.Current != null)
                {
                    if (String.IsNullOrEmpty(value))
                    {
                        HttpContext.Current.Items.Remove(ContextGuidKey);
                    }
                    else
                    {
                        HttpContext.Current.Items[ContextGuidKey] = value;
                    }
                }
            }
        }
 
        /// <summary>
        /// Gets the chart image handler interface reference.
        /// </summary>
        /// <returns></returns>
        private static IChartStorageHandler GetHandler()
        {
            return ChartHttpHandler.Settings.GetHandler();
        }
 
        /// <summary>
        /// Determines whether this instance is installed.
        /// </summary>
        internal static void EnsureInstalled()
        {
            EnsureInitialized(true);
            EnsureSessionIsClean();
        }
 
        /// <summary>
        /// Gets the handler URL.
        /// </summary>
        /// <returns></returns>
        private static String GetHandlerUrl()
        {
            // the handler have to be executed in current cxecution path in order to get proper user identity
            String appDir = Path.GetDirectoryName(HttpContext.Current.Request.CurrentExecutionFilePath ?? "").Replace("\\","/");
            if (!appDir.EndsWith("/", StringComparison.Ordinal))
            {
                appDir += "/";
            }
            return appDir + ChartHttpHandlerName + "?";
		}
 
 
        /// <summary>
        /// Gets the MIME type by resource url.
        /// </summary>
        /// <param name="resourceUrl">The resource URL.</param>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Globalization", "CA1308",
            Justification = "No security decision is being made on the ToLowerInvariant() call. It is being used to ensure the file extension is lowercase")]
        private static String GetMime(String resourceUrl)
        {
            String ext = Path.GetExtension(resourceUrl);
 
            ext = ext.ToLowerInvariant();
 
            if (ext == ".js")
            {
                return "text/javascript";
            }
            else if (ext == ".htm")
            {
                return "text/html";
            }
            else if (".css,.html,.xml".IndexOf(ext, StringComparison.Ordinal) != -1)
            {
                return "text/" + ext.Substring(1);
            }
            else if (".jpg;.jpeg;.gif;.png;.emf".IndexOf(ext, StringComparison.Ordinal) != -1)
            {
                string fmt = ext.Substring(1).Replace("jpg", "jpeg");
                return "image/" + fmt;
            }
            return "text/plain";
        }
 
        /// <summary>
        /// Generates the chart image file name (key).
        /// </summary>
        /// <param name="ext">The ext.</param>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        private static String GenerateKey(String ext)
        {
            String fmtKey = "chart" + _machineHash + "{0}." + ext;
            RingTimeTracker rt = RingTimeTrackerFactory.GetRingTracker(fmtKey);
			if (!String.IsNullOrEmpty(_controllerDirectory) && String.IsNullOrEmpty(Settings.FolderName))
			{
				return _controllerDirectory + @"\" + rt.GetNextKey();
			}
            return Settings.FolderName + rt.GetNextKey();
        }
 
		private static String KeyToUnc(String key)
		{
			if (!String.IsNullOrEmpty(key))
			{
				return key.Replace("/", @"\");
			}
			return key;
		}
		
		private static String KeyFromUnc(String key)
		{
			if (!String.IsNullOrEmpty(key))
			{
				return key.Replace(@"\", "/");
			}
			return key;
		}
 
        /// <summary>
        /// Gets a URL by specified request query, file key.
        /// </summary>
        /// <param name="query">The query.</param>
        /// <param name="fileKey">The file key.</param>
        /// <param name="currentGuid">The current GUID.</param>
        /// <returns></returns>
        private static String GetUrl(String query, String fileKey, string currentGuid)
        {
			return GetHandlerUrl() + query + "=" + KeyFromUnc(fileKey) + "&g=" + currentGuid;
        }
 
        /// <summary>
        /// Gets the image url.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="imageExt">The image extention.</param>
        /// <returns>Generated the image source URL</returns>
        [SuppressMessage("Microsoft.Globalization", "CA1308", 
            Justification="No security decision is being made on the ToLowerInvariant() call. It is being used to ensure the file extension is lowercase")]
        internal static String GetChartImageUrl(MemoryStream stream, String imageExt)
        {
            EnsureInitialized(true);
            // generates new guid
            string guidKey = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
            // set new guid in context
            CurrentGuidKey = guidKey;
            
            Int32 tryCounts = 10;
            while (tryCounts > 0)
            {
                tryCounts--;
                try
                {
                    String key = GenerateKey(imageExt.ToLowerInvariant());
                    IChartStorageHandler handler = Settings.GetHandler();
                    handler.Save(key, stream.ToArray());
                    if (!(handler is DefaultImageHandler))
                    {
                        Diagnostics.TraceWrite(SR.DiagnosticChartImageSaved(key), null);
                    }
                    Settings.FolderName = String.Empty;
                    // clear guid so is not accessable out of the scope;
                    CurrentGuidKey = String.Empty;
                    return ChartHttpHandler.GetUrl("i", key, guidKey);
                }
                catch (IOException) { }
                catch { throw;}
            }
            throw new IOException(SR.ExceptionHttpHandlerCanNotSave);
        }
 
        /// <summary>
        /// Ensures the session is clean.
        /// </summary>
        private static void EnsureSessionIsClean()
        {
            if (!_installed) return;
            if (Settings.StorageType == ChartHttpHandlerStorageType.Session)
            {
                IChartStorageHandler handler = ChartHttpHandler.Settings.GetHandler();
                foreach (RingTimeTracker tracker in RingTimeTrackerFactory.OpenedRingTimeTrackers())
                {
                        tracker.ForEach(true, delegate(RingItem item)
                        {
                            if (item.InUse && String.CompareOrdinal(Settings.ReadSessionKey(), item.SessionID) == 0)
                            {
                                handler.Delete(tracker.GetKey(item));
                                Diagnostics.TraceWrite(SR.DiagnosticChartImageDeleted(tracker.GetKey(item)), null);
                                item.InUse = false;
                            }
                        }
                    );
                }
            }
        }
        #endregion //Utilities
 
        #region Diagnostics
        
        private static void DiagnosticWriteAll(HttpContext context)
        {
            HtmlTextWriter writer;
            using (TextWriter w = new StringWriter(CultureInfo.CurrentCulture))
            {
                
                if (context.Request.Browser != null)
                    writer = context.Request.Browser.CreateHtmlTextWriter(w);
                else
                    writer = new Html32TextWriter(w);
                writer.Write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\r<html xmlns=\"http://www.w3.org/1999/xhtml\" >\n\r");
                writer.Write("<head>\r\n");
                writer.Write("<style type=\"text/css\">\r\n body, span, table, td, th, div, caption {font-family: Tahoma, Arial, Helvetica, sans-serif;font-size: 10pt;} caption {background-color:Black; color: White; font-weight:bold; padding: 4px; text-align:left; } \r\n</style>\r\n");
                writer.Write("</head>\r\n<body style=\"width:978px\">\r\n");
                writer.Write("<h2>" + SR.DiagnosticHeader + "</h2>\r\n<hr/><br/>\n\r");
                DiagnosticWriteSettings(writer);
                writer.Write("<hr/>");
                DiagnosticWriteActivity(writer);
                writer.Write("<br/><hr/>\n\r<span>");
                try
                {
                    writer.Write(typeof(Chart).AssemblyQualifiedName);
                }
                catch ( SecurityException ) {}
                writer.Write("</span></body>\r\n</html>\r\n");
                context.Response.Write(w.ToString());
            }
        }
 
        private static void DiagnosticWriteSettings(HtmlTextWriter writer)
        {
            writer.Write("<h4>" + SR.DiagnosticSettingsConfig(WebConfigurationManager.AppSettings[ChartHttpHandlerAppSection]) + "</h4>");
            GridView grid = CreateGridView( true);
            grid.Caption = SR.DiagnosticSettingsHeader;
            BoundField field = new BoundField();
            field.DataField = "Key";
            field.HeaderText = SR.DiagnosticSettingsKey;
            field.HeaderStyle.HorizontalAlign = HorizontalAlign.Left;
            grid.Columns.Add(field);
 
            field = new BoundField();
            field.DataField = "Value";
            field.HeaderText = SR.DiagnosticSettingsValue;
            field.HeaderStyle.HorizontalAlign = HorizontalAlign.Left;
 
            grid.Columns.Add(field);
            Dictionary<String, String> settings = new Dictionary<String, String>();
            
            settings.Add("StorageType", Settings.StorageType.ToString());
            settings.Add("TimeOut", Settings.Timeout.ToString());
            if (Settings.StorageType == ChartHttpHandlerStorageType.File)
            {
                settings.Add("Directory", Settings.Directory);
            }
            settings.Add("DeleteAfterServicing", Settings.DeleteAfterServicing.ToString());
            settings.Add("PrivateImages", Settings.PrivateImages.ToString());
            settings.Add("ImageOwnerKey", Settings.ImageOwnerKey.ToString());
            settings.Add("CustomHandlerName", Settings.CustomHandlerName);
            settings.Add(ChartHttpHandler.WebDevServerUseConfigSettings, String.Equals(Settings[ChartHttpHandler.WebDevServerUseConfigSettings], "true", StringComparison.OrdinalIgnoreCase).ToString());
 
            grid.DataSource = settings;
            grid.DataBind();
            
            grid.RenderControl(writer);
            
        }
 
        private static void DiagnosticWriteActivity(HtmlTextWriter writer)
        {
            GridView grid = CreateGridView( true);
            grid.Caption = SR.DiagnosticActivityHeader;
            BoundField field = new BoundField();
            field.DataField = "DateStamp";
            field.ItemStyle.VerticalAlign = VerticalAlign.Top;
            field.HeaderText = SR.DiagnosticActivityTime;
            field.HeaderStyle.HorizontalAlign = HorizontalAlign.Left;
            field.HeaderStyle.Width = 150;
            grid.Columns.Add(field);
 
            field = new BoundField();
            field.DataField = "Url";
            field.HeaderText = SR.DiagnosticActivityMessage;
            field.HeaderStyle.HorizontalAlign = HorizontalAlign.Left;
 
            grid.Columns.Add(field);
 
            grid.RowDataBound += new GridViewRowEventHandler(DiagnosticActivityGrid_RowDataBound);
 
            grid.DataSource = Diagnostics.Messages;
            grid.DataBind();
            grid.RenderControl(writer);
 
        }
 
        static void DiagnosticActivityGrid_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                Diagnostics.HandlerPageTraceInfo currentInfo = (Diagnostics.HandlerPageTraceInfo)e.Row.DataItem;
                TableCell cell = e.Row.Cells[1];
 
                cell.Controls.Add(new Label() { Text = currentInfo.Verb + "," + currentInfo.Url });
 
                GridView grid = CreateGridView(false);
                grid.Style[HtmlTextWriterStyle.MarginLeft] = "20px";
 
                grid.ShowHeader = false;
                
                BoundField field = new BoundField();
                field.DataField = "Text";
                field.HeaderStyle.HorizontalAlign = HorizontalAlign.Left;
                grid.Columns.Add(field);
 
                grid.DataSource = currentInfo.Events;
                grid.DataBind();
                cell.Controls.Add(grid);
            }
        }
 
        private static GridView CreateGridView(bool withAlternateStyle)
        {
            GridView result = new GridView();
 
            result.AutoGenerateColumns = false;
            result.CellPadding = 4;
            result.Font.Names = new string[] { "Tahoma", "Ariel" };
            result.Font.Size = new FontUnit(10, UnitType.Point);
            result.BorderWidth = 0;
            result.GridLines = GridLines.None;
            result.Width = new Unit(100, UnitType.Percentage);
 
            if (withAlternateStyle)
            {
                result.AlternatingRowStyle.BackColor = Color.White;
                result.RowStyle.BackColor = ColorTranslator.FromHtml("#efefef");
                result.RowStyle.ForeColor = Color.Black;
                result.AlternatingRowStyle.ForeColor = Color.Black;
            }
 
            result.HeaderStyle.BackColor = Color.Gray;
            result.HeaderStyle.ForeColor = Color.White;
            result.HeaderStyle.Font.Bold = true;
            return result;
        }
 
        #endregion //Diagnostics
 
        #endregion //Methods
 
        #region Properties
 
        /// <summary>
        /// Gets the chart image storage settings registred in web.config file under ChartHttpHandler key.
        /// </summary>
        /// <value>The settings.</value>
        public static ChartHttpHandlerSettings Settings
        {
            get
            {
                if (_parameters == null)
                {
                    _parameters = InitializeParameters();
                }
                return _parameters;
            }
        }
 
        #endregion //Properties
 
        #region IHttpHandler Members
 
        /// <summary>
        /// Gets a value indicating whether the <see cref="T:System.Web.UI.Page"/> object can be reused.
        /// </summary>
        /// <value></value>
        /// <returns>false in all cases. </returns>
        bool IHttpHandler.IsReusable
        {
            get { return true; }
        }
 
        /// <summary>
        /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"></see> interface.
        /// </summary>
        /// <param name="context">An <see cref="T:System.Web.HttpContext"></see> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
        void IHttpHandler.ProcessRequest(HttpContext context)
        {
            if (context.Request["i"] != null && ProcessSavedChartImage(context))
            {
                return;
            }
            else if (context.Request["trace"] != null && Diagnostics.IsTraceEnabled)
            {
                DiagnosticWriteAll(context);
                return;
            }
            else if (context.Request[handlerCheckQry] != null)
            {
                // handler execute test - returns no errors.
                return;
            }
            context.Response.StatusCode = 404;
            context.Response.StatusDescription = SR.ExceptionHttpHandlerImageNotFound;
        }
 
        #endregion
 
    }
 
    #region Enumerations
 
    /// <summary>
    /// Determines chart image storage medium
    /// </summary>
    public enum ChartHttpHandlerStorageType
    {
        /// <summary>
        /// Static into application memory
        /// </summary>
        InProcess,
 
        /// <summary>
        /// File system
        /// </summary>
        File,
        /// <summary>
        /// Using session as storage
        /// </summary>
        Session
 
    }
    /// <summary>
    /// Determines the image owner key for privacy protection.
    /// </summary>
    internal enum ImageOwnerKeyType
    {
        /// <summary>
        /// No privacy protection.
        /// </summary>
        None,
        /// <summary>
        /// The key will be automatically determined.
        /// </summary>
        Auto,
        /// <summary>
        /// The user name will be used as key.
        /// </summary>
        UserID,
        /// <summary>
        /// The AnonymousID will be used as key.
        /// </summary>
        AnonymousID,
        /// <summary>
        /// The SessionID will be used as key.
        /// </summary>
        SessionID
    }
 
    #endregion
 
    #region IChartStorageHandler interface
 
    /// <summary>
    /// Defines methods to manage rendered chart images in a storage.
    /// </summary>
    public interface IChartStorageHandler
    {
        /// <summary>
        /// Saves the data into external medium.
        /// </summary>
        /// <param name="key">Index key.</param>
        /// <param name="data">Image data.</param>
        void Save(String key, Byte[] data);
 
 
        /// <summary>
        /// Loads the data from external medium.
        /// </summary>
        /// <param name="key">Index key.</param>
        /// <returns>A byte array with image data</returns>
        Byte[] Load(String key);
 
 
        /// <summary>
        /// Deletes the data from external medium.
        /// </summary>
        /// <param name="key">Index key.</param>
        void Delete(String key);
 
        /// <summary>
        /// Checks for existence of data under specified key.
        /// </summary>
        /// <param name="key">Index key.</param>
        /// <returns>True if data exists under specified key</returns>
        bool Exists(String key);
    }
 
    #endregion
 
    #region ChartHttpHandlerSettings Class
 
    /// <summary>
    /// Enables access to the chart image storage settings.
    /// </summary>
#if ASPPERM_35
	[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
#endif
    public class ChartHttpHandlerSettings
    {
        #region Fields
 
        private StorageSettingsCollection _ssCollection = new StorageSettingsCollection();
        
        private string _sesionKey   = "chartKey-" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
 
        #endregion //Fields
 
        #region Properties
 
        private ChartHttpHandlerStorageType _chartImageStorage = ChartHttpHandlerStorageType.File;
 
        /// <summary>
        /// Gets or sets the chart image storage type.
        /// </summary>
        /// <value>The chart image storage.</value>
        public ChartHttpHandlerStorageType StorageType
        {
            get { return _chartImageStorage; }
            set { _chartImageStorage = value; }
        }
 
        private TimeSpan _timeout = TimeSpan.FromSeconds(30);
 
        /// <summary>
        /// Gets or sets the timeout.
        /// </summary>
        /// <value>The timeout.</value>
        public TimeSpan Timeout
        {
            get { return _timeout; }
            set { _timeout = value; }
        }
 
        private String _url = "~/";
        /// <summary>
        /// Gets or sets the URL.
        /// </summary>
        /// <value>The URL.</value>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]
        public String Url
        {
            get { return _url; }
            set { _url = value; }
        }
 
        private String _directory = String.Empty;
        /// <summary>
        /// Gets or sets the directory.
        /// </summary>
        /// <value>The directory.</value>
        public String Directory
        {
            get { return _directory; }
            set { _directory = value; }
        }
 
        private const String _folderKeyName = "{5FF3B636-70BA-4180-B7C5-FDD77D8FA525}";
        /// <summary>
        /// Gets or sets the folder which will be used for storing images under <see cref="Directory"/>.
        /// </summary>
        /// <value>The folder name.</value>
        public String FolderName
        {
            get 
            {
                if (HttpContext.Current != null && HttpContext.Current.Items.Contains(_folderKeyName))
                {
                    return (string)HttpContext.Current.Items[_folderKeyName];
                }
                return String.Empty; 
            }
            set 
            { 
                if (!String.IsNullOrEmpty(value))
                {
                    if (!(value.EndsWith("/", StringComparison.Ordinal) || value.EndsWith("\\", StringComparison.Ordinal)))
                    {
                        value += "\\";
                    }
                    this.ValidateUri(value);
                }
                if (HttpContext.Current != null)
                {
                    HttpContext.Current.Items[_folderKeyName] = value;
                }
            }
        }
 
        internal void ValidateUri(string key)
        {
            if (this.StorageType == ChartHttpHandlerStorageType.File)
            {
                FileInfo fi = new FileInfo(this.Directory + key);
                Uri directory = new Uri(this.Directory);
                Uri combinedDirectory = new Uri(fi.FullName); 
                if (directory.IsBaseOf(combinedDirectory))
                {
                    // it is fine.
                    return;
                }
                throw new UnauthorizedAccessException(SR.ExceptionHttpHandlerInvalidLocation);  
            }
        }
 
        private String _customHandlerName = typeof(DefaultImageHandler).FullName;
        /// <summary>
        /// Gets or sets the name of the custom handler.
        /// </summary>
        /// <value>The name of the custom handler.</value>
        public String CustomHandlerName
        {
            get { return _customHandlerName; }
            set { _customHandlerName = value; }
        }
 
 
        private Type _customHandlerType = null;
        /// <summary>
        /// Gets the type of the custom handler.
        /// </summary>
        /// <value>The type of the custom handler.</value>
        public Type HandlerType
        {
            get
            {
                if (this._customHandlerType == null)
                {
                    this._customHandlerType = Type.GetType(this.CustomHandlerName, true);
                }
                return this._customHandlerType;
            }
        }
        
        /// <summary>
        /// Gets a value indicating whether the handler will utilize private images.
        /// </summary>
        /// <value><c>true</c> if the handler will utilize private images; otherwise, <c>false</c>.</value>
        /// <remarks>
        /// When PrivateImages is set the handler will not return images out of session scope and 
        /// the client will not be able to download somebody else's images. This is default behavoiur.
        /// </remarks>
        public bool PrivateImages
        {
            get
            {
                return ImageOwnerKey != ImageOwnerKeyType.None;
            }
        }
 
 
 
        /// <summary>
        /// Gets a settings parameter with the specified name registred in web.config file under ChartHttpHandler key.
        /// </summary>
        /// <value></value>
        public string this[string name]
        {
            get
            {
                return this._ssCollection[name];
            }
        }
        
        #endregion //Properties
 
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="T:StorageSettings"/> class.
        /// </summary>
        internal ChartHttpHandlerSettings()
        {
            ImageOwnerKey = ImageOwnerKeyType.Auto;
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="T:ChartHttpHandlerParameters"/> class.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        internal ChartHttpHandlerSettings(String parameters) : this()
        {
            this.ParseParams(parameters);
            this._ssCollection.SetReadOnly(true);
        }
 
        #endregion //Constructors
 
        #region Methods
 
        private ConstructorInfo _handlerConstructor = null;
        IChartStorageHandler _storageHandler = null;
        /// <summary>
        /// Creates the handler instance.
        /// </summary>
        /// <returns></returns>
        internal IChartStorageHandler GetHandler()
        {
            if (_storageHandler == null)
            {
                if (this._handlerConstructor == null)
                {
                    this.InspectHandlerLoader();
                }
                _storageHandler = this._handlerConstructor.Invoke(new object[0]) as IChartStorageHandler;
            }
            return _storageHandler;
        }
 
        /// <summary>
        /// Inspects the handler if it is valid.
        /// </summary>
        private void InspectHandlerLoader()
        {
            this._handlerConstructor = this.HandlerType.GetConstructor(
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
                null,
                new Type[0],
                new ParameterModifier[0]);
            if (this._handlerConstructor == null)
            {
                throw new InvalidOperationException( SR.ExceptionHttpHandlerCanNotLoadType( this.HandlerType.FullName ));
            }
            if (this.GetHandler() == null)
            {
                throw new InvalidOperationException(SR.ExceptionHttpHandlerImageHandlerInterfaceUnsupported(ChartHttpHandler.Settings.HandlerType.FullName));
            }
        }
 
        /// <summary>
        /// Parses the params from web.config file key.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        private void ParseParams(String parameters)
        {
            if (!String.IsNullOrEmpty(parameters))
            {
 
                String[] pairs = parameters.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                for (int index = 0; index < pairs.Length; index++)
                {
                    String item = pairs[index].Trim();
                    int eqPositon = item.IndexOf('=');
                    if (eqPositon != -1)
                    {
                        String name = item.Substring(0, eqPositon).Trim();
                        String value = item.Substring(eqPositon + 1).Trim();
                        this._ssCollection.Add(name, value);
                        if (name.StartsWith("stor", StringComparison.OrdinalIgnoreCase))
                        {
                            if (value.StartsWith("inproc", StringComparison.OrdinalIgnoreCase) || value.StartsWith("memory", StringComparison.OrdinalIgnoreCase))
                            {
                                this.StorageType = ChartHttpHandlerStorageType.InProcess;
                            }
                            else if (value.StartsWith("file", StringComparison.OrdinalIgnoreCase))
                            {
                                this.StorageType = ChartHttpHandlerStorageType.File;
                            }
                            else if (value.StartsWith("session", StringComparison.OrdinalIgnoreCase))
                            {
                                this.StorageType = ChartHttpHandlerStorageType.Session;
                            }
                            else
                            {
                                throw new System.Configuration.SettingsPropertyWrongTypeException(SR.ExceptionHttpHandlerParameterUnknown(name, value));
                            }
                        }
                        else if (name.StartsWith("url", StringComparison.OrdinalIgnoreCase))
                        {
                            if (!value.EndsWith("/", StringComparison.Ordinal))
                            {
                                value += "/";
                            }
                            this.Url = value;
                        }
                        else if (name.StartsWith("dir", StringComparison.OrdinalIgnoreCase))
                        {
                            this.Directory = value;
                        }
                        else if (name.StartsWith("time", StringComparison.OrdinalIgnoreCase))
                        {
                            try
                            {
                                int seconds = Int32.Parse(value, CultureInfo.InvariantCulture);
                                if (seconds < -1)
                                {
                                    throw new System.Configuration.SettingsPropertyWrongTypeException(SR.ExceptionHttpHandlerValueInvalid);
                                }
                                if (seconds == -1)
                                {
                                    this.Timeout = TimeSpan.MaxValue;
                                }
                                else
                                {
                                    this.Timeout = TimeSpan.FromSeconds(seconds);
                                }
                            }
                            catch (Exception exception)
                            {
                                throw new System.Configuration.SettingsPropertyWrongTypeException(SR.ExceptionHttpHandlerTimeoutParameterInvalid, exception);
                            }
                        }
                        else if (name.StartsWith("handler", StringComparison.OrdinalIgnoreCase))
                        {
                            this.CustomHandlerName = value;
                        }
                        else if (name.StartsWith("privateImages", StringComparison.OrdinalIgnoreCase))
                        {
                            bool privateImg = true;
                            if (Boolean.TryParse(value, out privateImg) && !privateImg)
                            {
                                ImageOwnerKey = ImageOwnerKeyType.None;
                            }
                        }
                        else if (name.StartsWith("imageOwnerKey", StringComparison.OrdinalIgnoreCase))
                        {
                            try
                            {
                                ImageOwnerKey = (ImageOwnerKeyType)Enum.Parse(typeof(ImageOwnerKeyType), value, true);
                            }
                            catch (ArgumentException)
                            {
                                throw new System.Configuration.SettingsPropertyWrongTypeException(SR.ExceptionHttpHandlerParameterInvalid(name, value));
                            }
                        }
 
                    }
                }
            }
            this.Inspect();
        }
 
        /// <summary>
        /// Determines whether web dev server is active.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if web dev server active; otherwise, <c>false</c>.
        /// </returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "GetCurrentProcess will fail if there is no access. This is by design. ")]
        // VSTS: 5176	Security annotation violations in System.Web.DataVisualization.dll
        [SecuritySafeCritical]
        private static bool IsWebDevActive()
        {
            try
            {
                Process process = Process.GetCurrentProcess();
                if (process.ProcessName.StartsWith("WebDev.WebServer", StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
                if (process.ProcessName.StartsWith("ii----press", StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            catch (SecurityException)
            {
            }
            return false;
        }
        /// <summary>
        /// Inspects and validates this instance after loading params.
        /// </summary>
        internal void Inspect()
        {
            switch (this.StorageType)
            {
                case ChartHttpHandlerStorageType.InProcess:
 
                    break;
 
                case ChartHttpHandlerStorageType.File:
 
                    if (IsWebDevActive() && !( String.Compare(this[ChartHttpHandler.WebDevServerUseConfigSettings], "true", StringComparison.OrdinalIgnoreCase) == 0))
                    {
                        this.StorageType = ChartHttpHandlerStorageType.InProcess;
                        break;
                    }
 
                    if (String.IsNullOrEmpty(this.Url))
                    {
                        throw new ArgumentException(SR.ExceptionHttpHandlerUrlMissing);
                    }
                    
                    String fileDirectory = this.Directory;
                    if (String.IsNullOrEmpty(fileDirectory))
                    {
                        try
                        {
                            fileDirectory = HttpContext.Current.Server.MapPath(this.Url);
                        }
                        catch (Exception exception)
                        {
                            throw new InvalidOperationException(SR.ExceptionHttpHandlerUrlInvalid, exception);
                        }
                    }
                    fileDirectory = fileDirectory.Replace("/", "\\");
                    if (!fileDirectory.EndsWith("\\", StringComparison.Ordinal))
                    {
                        fileDirectory += "\\";
                    }
 
                    if (!System.IO.Directory.Exists(fileDirectory))
                    {
                        throw new DirectoryNotFoundException(SR.ExceptionHttpHandlerTempDirectoryInvalid(fileDirectory));
                    }
                    Exception thrown = null;
                    try
                    {
                        String testFileName = fileDirectory + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
                        using (FileStream fileStream = File.Create(testFileName)) { }
                        File.Delete(testFileName);
 
                    }
                    catch (DirectoryNotFoundException exception)
                    {
                        thrown = exception;
                    }
                    catch (NotSupportedException exception)
                    {
                        thrown = exception;
                    }
                    catch (PathTooLongException exception)
                    {
                        thrown = exception;
                    }
                    catch (UnauthorizedAccessException exception)
                    {
                        thrown = exception;
                    }
 
                    if (thrown != null)
                    {
                        throw new UnauthorizedAccessException(SR.ExceptionHttpHandlerTempDirectoryUnaccesible(fileDirectory));
                    }
 
                    this.Directory = fileDirectory;
                    break;
 
 
            }
            if (!String.IsNullOrEmpty(this.CustomHandlerName))
            {
                this.InspectHandlerLoader();
            }
        }
 
        /// <summary>
        /// Prepares the design time params.
        /// </summary>
        internal void PrepareDesignTime()
        {
            this.StorageType = ChartHttpHandlerStorageType.File;
            this.Timeout = TimeSpan.FromSeconds(3); ;
            this.Url = Path.GetTempPath();
            this.Directory = Path.GetTempPath();
        }
 
        internal string ReadSessionKey()
        {
            if (HttpContext.Current.Session != null)
            {
                // initialize session (if is empty any postsequent request will have different id);
                if (HttpContext.Current.Session.IsNewSession)
                {
                    if (HttpContext.Current.Session.IsReadOnly)
                    {
                        return string.Empty;
                    }
                    HttpContext.Current.Session[this._sesionKey] = 0;
                }
                return HttpContext.Current.Session.SessionID;
            }
            return String.Empty;
        }
        
        internal string GetPrivacyKey( out ImageOwnerKeyType keyType )
        {
            if (ImageOwnerKey == ImageOwnerKeyType.None)
            {
                keyType = ImageOwnerKeyType.None;                
                return String.Empty;
            }
            if (HttpContext.Current != null)
            {
                switch (ImageOwnerKey)
                {
                    case ImageOwnerKeyType.Auto:
                        if (HttpContext.Current.User.Identity.IsAuthenticated)
                        {
                            keyType = ImageOwnerKeyType.UserID;
                            return HttpContext.Current.User.Identity.Name;
                        }
                        if (!String.IsNullOrEmpty(HttpContext.Current.Request.AnonymousID))
                        {
                            keyType = ImageOwnerKeyType.AnonymousID;
                            return HttpContext.Current.Request.AnonymousID;
                        }
                        string sessionId = ReadSessionKey();
                        keyType = String.IsNullOrEmpty(sessionId) ? ImageOwnerKeyType.None : ImageOwnerKeyType.SessionID;
                        return sessionId;
 
                    case ImageOwnerKeyType.UserID:
                        if (!HttpContext.Current.User.Identity.IsAuthenticated)
                        {
                            throw new InvalidOperationException(SR.ExceptionHttpHandlerPrivacyKeyInvalid("ImageOwnerKey", ImageOwnerKey.ToString()));
                        }
                        keyType = ImageOwnerKeyType.UserID;
                        return HttpContext.Current.User.Identity.Name;
 
                    case ImageOwnerKeyType.AnonymousID:
                        if (String.IsNullOrEmpty(HttpContext.Current.Request.AnonymousID))
                        {
                            throw new InvalidOperationException(SR.ExceptionHttpHandlerPrivacyKeyInvalid("ImageOwnerKey", ImageOwnerKey.ToString()));
                        }
                        keyType = ImageOwnerKeyType.AnonymousID;
                        return HttpContext.Current.Request.AnonymousID;
 
                    case ImageOwnerKeyType.SessionID:
                        if (HttpContext.Current.Session == null)
                        {
                            throw new InvalidOperationException(SR.ExceptionHttpHandlerPrivacyKeyInvalid("ImageOwnerKey", ImageOwnerKey.ToString()));
                        }
                        keyType = ImageOwnerKeyType.SessionID;
                        return ReadSessionKey();
 
                    default:
                        Debug.Fail("Unknown ImageOwnerKeyType.");
                        break;
                }
            }
            keyType = ImageOwnerKeyType.None;
            return string.Empty;
        }
 
        internal string PrivacyKey
        {
            get
            {
                ImageOwnerKeyType keyType;
                return GetPrivacyKey(out keyType);
            }
        }
 
        internal bool DeleteAfterServicing
        {
            get
            {
                // default, if is missing in config,  is true.
                return !(String.Compare(this["DeleteAfterServicing"], "false", StringComparison.OrdinalIgnoreCase) == 0); 
            }
        }
 
        /// <summary>
        /// Gets or sets the image owner key type.
        /// </summary>
        /// <value>The image owner key.</value>
        internal ImageOwnerKeyType ImageOwnerKey { get; set; }
 
        #endregion //Methods
 
        #region SettingsCollection Class
 
        private class StorageSettingsCollection : NameValueCollection
        {
            public StorageSettingsCollection()
                : base(StringComparer.OrdinalIgnoreCase)
            {
            }
            internal void SetReadOnly(bool flag)
            {
                this.IsReadOnly = flag;
            }
        }
 
        #endregion //SettingsCollection Class
    }
 
    #endregion ChartHttpHandlerParameters
 
    #region DefaultImageHandler Class
 
    /// <summary>
    /// Default implementation of ChartHttpHandler.IImageHandler interface
    /// </summary>
    internal class DefaultImageHandler : IChartStorageHandler
    {
 
        #region Fields
        // Hashtable for storage
        private static Hashtable _storageData = new Hashtable();
        // lock object
        private static ReaderWriterLock _rwl = new ReaderWriterLock();
        // max access timeout
        private const int accessTimeout = 10000;
 
        static string _privacyKeyName = "_pk";
        static byte[] _privacyMarker = (new Guid("332E3AB032904bceA82B249C25E65CB6")).ToByteArray();
        static string _sessionKeyPrefix = "chart-3ece47b3-9481-4b22-ab45-ab669972eb79";
 
        #endregion //Fields
 
        #region Constructors
 
        /// <summary>
        /// Initializes a new instance of the <see cref="T:DefaultImageHandler"/> class.
        /// </summary>
        internal DefaultImageHandler()
        {
        }
        #endregion //Constructors
 
        #region Members
        /// <summary>
        /// Nots the type of the supported storage.
        /// </summary>
        /// <param name="settings">The settings.</param>
        private void NotSupportedStorageType(ChartHttpHandlerSettings settings)
        {
            throw new NotSupportedException( SR.ExceptionHttpHandlerStorageTypeUnsupported( settings.StorageType.ToString() ));
        }
 
        #endregion //Members
 
        #region Methods
 
        /// <summary>
        /// Returns privacy hash which will be save in the file.
        /// </summary>
        /// <returns>A byte array of hash data</returns>
        private static byte[] GetHashData()
        {
                string currentGuid = ChartHttpHandler.CurrentGuidKey;
                string sessionID = ChartHttpHandler.Settings.PrivacyKey;
 
                if (String.IsNullOrEmpty(sessionID))
                {
                    return new byte[0];
                }
 
                byte[] data = Encoding.UTF8.GetBytes(sessionID + "/" + currentGuid);
 
                using (SHA256 sha = new SHA256CryptoServiceProvider())
                {
                    return sha.ComputeHash(data);
                }
 
        }
 
        private static bool CompareBytes(byte[] a, byte[] b)
        {
            if (a.Length != b.Length)  return false;
            for (int i = 0; i < a.Length; i++)
            {
                if (a[i] != b[i]) return false;
            }
            return true;
        }
 
        private static string GetSessionImageKey(string key)
        {
            // all session variables starts with _sessionKeyPrefix to avoid direct access to session by passing image key in Url query.
            return _sessionKeyPrefix + key;
        }
 
        #endregion //Methods
 
        #region ImageHandler Members
 
        /// <summary>
        /// Stores the data into external medium.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="data">The data.</param>
        void IChartStorageHandler.Save(String key, Byte[] data)
        {
            ChartHttpHandlerSettings settings = ChartHttpHandler.Settings;
            ImageOwnerKeyType imageOwnerKeyType = ImageOwnerKeyType.None;
            string privacyKey = settings.GetPrivacyKey(out imageOwnerKeyType);
            if (settings.StorageType == ChartHttpHandlerStorageType.InProcess)
            {
                _rwl.AcquireWriterLock(accessTimeout);
                try
                {
                    _storageData[key] = data;
                    if (settings.PrivateImages && !String.IsNullOrEmpty(privacyKey))
                    {
                        _storageData[key + _privacyKeyName] = privacyKey;
                        Diagnostics.TraceWrite( SR.DiagnosticChartImageSavedPrivate(key, imageOwnerKeyType.ToString()), null);
                    }
                    else
                        Diagnostics.TraceWrite(SR.DiagnosticChartImageSaved(key), null);
                }
                finally
                {
                    _rwl.ReleaseWriterLock();
                }
            }
            else if (settings.StorageType == ChartHttpHandlerStorageType.File)
            {
                using (FileStream stream = File.Create(settings.Directory + key))
                {
                    stream.Write(data, 0, data.Length);
                    if (settings.PrivateImages && !String.IsNullOrEmpty(privacyKey))
                    {
                        byte[] privacyData = GetHashData();
                        stream.Write(privacyData, 0, privacyData.Length);
                        // we will put a marker at the end of the file;
                        stream.Write(_privacyMarker, 0, _privacyMarker.Length);
                        Diagnostics.TraceWrite(SR.DiagnosticChartImageSavedPrivate(key, imageOwnerKeyType.ToString()), null);
                    }
                    else
                        Diagnostics.TraceWrite(SR.DiagnosticChartImageSaved(key), null);
                }
            }
            else if (settings.StorageType == ChartHttpHandlerStorageType.Session)
            {
                HttpContext.Current.Session[GetSessionImageKey(key)] = data;
                Diagnostics.TraceWrite(SR.DiagnosticChartImageSaved(key), null);
            }
            else this.NotSupportedStorageType(settings);
        }
 
 
        /// <summary>
        /// Retrieves the data from external medium.
        /// </summary>
        /// <param name="key">The key.</param>
        Byte[] IChartStorageHandler.Load( String key)
        {
            ChartHttpHandlerSettings settings = ChartHttpHandler.Settings;
            ImageOwnerKeyType imageOwnerKeyType = ImageOwnerKeyType.None;
            string privacyKey = settings.GetPrivacyKey(out imageOwnerKeyType);
            Byte[] data = new Byte[0];
            if (settings.StorageType == ChartHttpHandlerStorageType.InProcess)
            {
                 _rwl.AcquireReaderLock(accessTimeout);
                 try
                 {
                     if (settings.PrivateImages)
                     {
                         if (!String.IsNullOrEmpty(privacyKey))
                         {
                             if (!String.Equals((string)_storageData[key + _privacyKeyName], privacyKey, StringComparison.Ordinal))
                             {
                                 Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailPrivacyFail(imageOwnerKeyType.ToString())), null);
                                 return data;
                             }
                         }
                         else
                         {
                             if (!String.IsNullOrEmpty((string)_storageData[key + _privacyKeyName]))
                             {
                                 Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailPrivacyFail(imageOwnerKeyType.ToString())), null);
                                 return data;
                             }
                         }
                     }
                     data = (Byte[])_storageData[key];
                     if (data == null)
                     {
                         Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailNotFound), null);
                     }
                 }
                 finally
                 {
                     _rwl.ReleaseReaderLock();
                 }
            }
            else if (settings.StorageType == ChartHttpHandlerStorageType.File)
            {
                settings.ValidateUri(key);
                if (File.Exists(settings.Directory + key))
                {
                    using (FileStream fileStream = File.OpenRead(settings.Directory + key))
                    {
                        byte[] fileData = new byte[fileStream.Length];
                        fileStream.Read(fileData, 0, fileData.Length);
                        using (MemoryStream stream = new MemoryStream(fileData))
                        {
                            int streamCut = 0;
                            if (settings.PrivateImages)
                            {
                                // read the marker first
                                byte[] privacyMarkerStream = new Byte[_privacyMarker.Length];
 
                                streamCut += _privacyMarker.Length;
                                stream.Seek(stream.Length - streamCut, SeekOrigin.Begin);
                                stream.Read(privacyMarkerStream, 0, privacyMarkerStream.Length);
 
                                if (!String.IsNullOrEmpty(privacyKey))
                                {
                                    byte[] privacyData = GetHashData();
                                    streamCut += privacyData.Length;
                                    byte[] privacyDataFromStream = new Byte[privacyData.Length];
                                    stream.Seek(stream.Length - streamCut, SeekOrigin.Begin);
                                    stream.Read(privacyDataFromStream, 0, privacyDataFromStream.Length);
 
                                    if (!CompareBytes(privacyDataFromStream, privacyData))
                                    {
                                        Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailPrivacyFail(imageOwnerKeyType.ToString())), null);
                                        return data;
                                    }
                                }
                                else
                                {
                                    // this image is marked as private - check end return null if fails
                                    if (String.Equals(
                                        Encoding.Unicode.GetString(privacyMarkerStream),
                                        Encoding.Unicode.GetString(_privacyMarker),
                                        StringComparison.Ordinal))
                                    {
                                        Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailPrivacyFail(imageOwnerKeyType.ToString())), null);
                                        return data;
                                    }
                                    // its fine ( no user is stored )
                                    streamCut = 0;
                                }
                            }
                            stream.Seek(0, SeekOrigin.Begin);
                            data = new Byte[(int)stream.Length - streamCut];
                            stream.Read(data, 0, (int)data.Length);
                        }
                    }
                }
                else
                    Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailNotFound), null);
            }
            else if (settings.StorageType == ChartHttpHandlerStorageType.Session)
            {
                data = (Byte[])HttpContext.Current.Session[GetSessionImageKey(key)];
            }
            else this.NotSupportedStorageType(settings);
            return data;
 
        }
 
 
        /// <summary>
        /// Removes the data from external medium.
        /// </summary>
        /// <param name="key">The key.</param>
        void IChartStorageHandler.Delete(String key)
        {
            ChartHttpHandlerSettings settings = ChartHttpHandler.Settings;
            if (settings.StorageType == ChartHttpHandlerStorageType.InProcess)
            {
 
                _rwl.AcquireWriterLock(accessTimeout);
                try
                {
                    _storageData.Remove(key);
                    _storageData.Remove(key + _privacyKeyName);
                }
                finally
                {
                    _rwl.ReleaseWriterLock();
                }
            }
            else if (settings.StorageType == ChartHttpHandlerStorageType.File)
            {
                File.Delete(settings.Directory + key);
            }
            else if (settings.StorageType == ChartHttpHandlerStorageType.Session)
            {
                HttpContext.Current.Session.Remove(GetSessionImageKey(key));
            }
            else this.NotSupportedStorageType(settings);
        }
 
        /// <summary>
        /// Checks for existence the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        bool IChartStorageHandler.Exists(String key)
        {
            ChartHttpHandlerSettings settings = ChartHttpHandler.Settings;
            if (settings.StorageType == ChartHttpHandlerStorageType.InProcess)
            {
                _rwl.AcquireReaderLock(accessTimeout);
                try
                {
                    return _storageData.Contains(key);
                }
                finally
                {
                    _rwl.ReleaseReaderLock();
                }
            }
            else if (settings.StorageType == ChartHttpHandlerStorageType.File)
            {
                return File.Exists(settings.Directory + key);
            }
            else if (settings.StorageType == ChartHttpHandlerStorageType.Session)
            {
                return HttpContext.Current.Session[GetSessionImageKey(key)] is Byte[];
            }
            else this.NotSupportedStorageType(settings);
            return false;
        }
 
        #endregion
 
    }
 
    #endregion //DefaultImageHandler Class
 
    #region RingTimeTracker class
 
    /// <summary>
    /// RingItem contains time span of creation timedate and  index for key generation.
    /// </summary>
    internal class RingItem
    {
        internal Int32 Index;
        internal DateTime Created = DateTime.Now;
        internal string   SessionID = String.Empty;
        internal bool InUse;
        /// <summary>
        /// Initializes a new instance of the <see cref="T:RingItem"/> class.
        /// </summary>
        /// <param name="index">The index.</param>
        internal RingItem( int index)
        {
            this.Index = index;
        }
    }
    /// <summary>
    /// RingTimeTracker is a helper class for generating keys and tracking RingItem. 
    /// Contains linked list queue and tracks exprired items.
    /// </summary>
    internal class RingTimeTracker
    {
        #region Fields
        // the item life span
        private TimeSpan _itemLifeTime = TimeSpan.FromSeconds(360);
        // last requested RingItem
        private LinkedListNode<RingItem> _current;
        // default key format to format names
        private String _keyFormat = String.Empty;
        // LinkedList with ring items
        private LinkedList<RingItem> _list = new LinkedList<RingItem>();
        // Record session ID
        private bool _recordSessionID = false;
 
        #endregion //Fields
 
        #region Constructors
 
        /// <summary>
        /// Initializes a new instance of the <see cref="T:RingTimeTracker"/> class.
        /// </summary>
        /// <param name="itemLifeTime">The item life time.</param>
        /// <param name="keyFormat">The key format.</param>
        /// <param name="recordSessionID">if set to <c>true</c> the session ID will be recorded.</param>
        internal RingTimeTracker(TimeSpan itemLifeTime, String keyFormat, bool recordSessionID)
        {
            System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(keyFormat));
            this._itemLifeTime = itemLifeTime;
            this._keyFormat = keyFormat;
            this._list.AddLast(new RingItem(_list.Count));
            this._current = this._list.First;
            this._current.Value.Created = DateTime.Now - this._itemLifeTime - TimeSpan.FromSeconds(1);
            this._recordSessionID = recordSessionID;
        }
 
        #endregion //Constructors
 
        #region Methods
 
        
 
        /// <summary>
        /// Determines whether the specified item is expired.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="now">The now.</param>
        /// <returns>
        /// 	<c>true</c> if the specified item is expired; otherwise, <c>false</c>.
        /// </returns>
        internal bool IsExpired(RingItem item, DateTime now)
        {
            TimeSpan elapsed = (now - item.Created);
            return elapsed > this._itemLifeTime;
        }
 
 
        /// <summary>
        /// Gets the next key.
        /// </summary>
        /// <returns></returns>
        internal String GetNextKey()
        {
            DateTime now = DateTime.Now;
            lock (this)
            {
                if ( !this.IsExpired(this._current.Value, now))
                {
                    if (this._current.Next == null)
                    {
                        if (!this.IsExpired(this._list.First.Value, now))
                        {
                            this._list.AddLast(new RingItem(_list.Count));
                            this._current = this._list.Last;
                        }
                        else
                        {
                            this._current = this._list.First;
                        }
                    }
                    else
                    {
                        if (!this.IsExpired(this._current.Next.Value, now))
                        {
                            this._list.AddAfter(this._current, new RingItem(_list.Count));
                        }
                        this._current = this._current.Next;
                    }
                }
                this._current.Value.Created = now;
                if (this._recordSessionID)
                {
                    this._current.Value.SessionID = ChartHttpHandler.Settings.ReadSessionKey();
                    this._current.Value.InUse = true;
                }
                return this.GetCurrentKey();
            }
        }
 
        /// <summary>
        /// Gets the current key.
        /// </summary>
        /// <returns></returns>
        internal String GetCurrentKey()
        {
            return String.Format( CultureInfo.InvariantCulture, this._keyFormat, this._current.Value.Index);
        }
 
        /// <summary>
        /// Gets the key.
        /// </summary>
        /// <param name="ringItem">The ring item.</param>
        /// <returns></returns>
        internal String GetKey(RingItem ringItem)
        {
            return String.Format(CultureInfo.InvariantCulture, this._keyFormat, ringItem.Index);
        }
 
        /// <summary>
        /// Do Action for each item.
        /// </summary>
        /// <param name="onlyExpired">if set to <c>true</c> do action for only expired items.</param>
        /// <param name="action">The action.</param>
        public void ForEach(bool onlyExpired, Action<RingItem> action)
        {
            if (action == null)
            {
                throw new ArgumentNullException("action");
            }
            DateTime now = DateTime.Now;
            lock (this)
            {
                foreach (RingItem item in this._list)
                {
                    if (onlyExpired)
                    {
                        if (this.IsExpired(item, now))
                        {
                            action(item);
                        }
                    }
                    else
                    {
                        action(item);
                    }
                }
            }
        }
 
        #endregion //Methods
 
    }
    #endregion //RingTracker class
 
    #region  RingTimeTrackerFactory Class
 
    /// <summary>
    /// RingTimeTrackerFactory contains static list of RingTimeTracker for each key formats
    /// </summary>
    internal static class RingTimeTrackerFactory
    {
        
        private static ListDictionary _ringTrackers = new ListDictionary();
        private static Object _lockObject = new Object();
 
        /// <summary>
        /// Gets the ring tracker by specified key format.
        /// </summary>
        /// <param name="keyFormat">The key format.</param>
        /// <returns></returns>
        internal static RingTimeTracker GetRingTracker(String keyFormat)
        {
            if (_ringTrackers.Contains(keyFormat))
            {
                return (RingTimeTracker)_ringTrackers[keyFormat];
            }
            lock (_lockObject)
            {
                if (_ringTrackers.Contains(keyFormat))
                {
                    return (RingTimeTracker)_ringTrackers[keyFormat];
                }
                RingTimeTracker result = new RingTimeTracker(ChartHttpHandler.Settings.Timeout, keyFormat,ChartHttpHandler.Settings.StorageType == ChartHttpHandlerStorageType.Session);
                _ringTrackers.Add(keyFormat, result);
                return result;
            }
        }
 
        internal static IList OpenedRingTimeTrackers()
        {
            lock (_lockObject)
            {
                return new ArrayList(_ringTrackers.Values);
            }
        }
        
    }
 
    #endregion  //RingTimeTrackerFactory Class
 
    #region Diagnostics class
 
    /// <summary>
    /// Contains helpres methods for diagnostics.
    /// </summary>
    internal static class Diagnostics
    {
        /// <summary>
        /// Trace category
        /// </summary>
        const string ChartCategory = "chart.handler";
        /// <summary>
        /// Name of context item which contain the current trace item 
        /// </summary>
        const string ContextID = "Trace-{89FA5660-BD13-4f1b-8C7C-355CEC92CC7E}";
        /// <summary>
        /// Used for syncronizing.
        /// </summary>
        static object _lockObject = new object();
        /// <summary>
        /// Limit of trace messages in the history.
        /// </summary>
        const int MessageLimit = 20;
        /// <summary>
        /// Collection of request messages.
        /// </summary>
        static List<HandlerPageTraceInfo> _messages = new List<HandlerPageTraceInfo>(MessageLimit);
 
        /// <summary>
        /// Contains request info
        /// </summary>
        public class HandlerPageTraceInfo
        {
            /// <summary>
            /// Events collection in this request.
            /// </summary>
            private List<ChartHandlerEvents> _events = new List<ChartHandlerEvents>();
            /// <summary>
            /// Initializes a new instance of the <see cref="HandlerPageTraceInfo"/> class.
            /// </summary>
            public HandlerPageTraceInfo()
            {
                if (HttpContext.Current != null)
                {
                    DateStamp = DateTime.Now;
                    if (HttpContext.Current.Request != null)
                    {
                        Url = HttpContext.Current.Request.Url.ToString();
                        Verb = HttpContext.Current.Request.HttpMethod;
                    }
                }
            }
            /// <summary>
            /// Gets or sets the date stamp.
            /// </summary>
            /// <value>The date stamp.</value>
            public DateTime DateStamp { get; private set; }
            /// <summary>
            /// Gets or sets the URL.
            /// </summary>
            /// <value>The URL.</value>
            public string Url { get; private set; }
            /// <summary>
            /// Gets or sets the verb.
            /// </summary>
            /// <value>The verb.</value>
            public string Verb { get; private set; }
            /// <summary>
            /// Gets the events.
            /// </summary>
            /// <value>The events.</value>
            public IList<ChartHandlerEvents> Events
            {
                get
                {
                    return _events.AsReadOnly();
                }
            }
            /// <summary>
            /// Adds a trace info item.
            /// </summary>
            /// <param name="message">The message.</param>
            /// <param name="errorInfo">The error info.</param>
            internal void AddTraceInfo(string message, string errorInfo)
            {
                lock (_events)
                {
                    _events.Add(new ChartHandlerEvents()
                        {
                            Message = message,
                            ErrorInfo = errorInfo
                        }
                     );
                }
            }
        }
 
        /// <summary>
        /// Contains an event in particural request.
        /// </summary>
        public class ChartHandlerEvents
        {
            /// <summary>
            /// Gets or sets the message.
            /// </summary>
            /// <value>The message.</value>
            public string Message { get; set; }
            /// <summary>
            /// Gets or sets the error info.
            /// </summary>
            /// <value>The error info.</value>
            public string ErrorInfo { get; set; }
            /// <summary>
            /// Gets the text.
            /// </summary>
            /// <value>The text.</value>
            public string Text { get { return Message + ErrorInfo; } }
        }
 
        /// <summary>
        /// Writes message in the trace.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="errorInfo">The error info.</param>
        internal static void TraceWrite( string message, Exception errorInfo)
        {
            if (IsTraceEnabled)
            {
                HttpContext.Current.Trace.Write(ChartCategory, message, errorInfo);
                if (CurrentTraceInfo != null)
                {
                    CurrentTraceInfo.AddTraceInfo(message, errorInfo != null ? errorInfo.ToString() : String.Empty);
                }
            }
        }
 
        /// <summary>
        /// Gets the current trace info.
        /// </summary>
        /// <value>The current trace info.</value>
        private static HandlerPageTraceInfo CurrentTraceInfo
        {
            get
            {
                lock (_lockObject)
                {
                    if (HttpContext.Current != null)
                    {
                        if (HttpContext.Current.Items[Diagnostics.ContextID] == null)
                        {
                            HandlerPageTraceInfo pageTrace = new HandlerPageTraceInfo();
                            _messages.Add(pageTrace);
                            if (_messages.Count > MessageLimit)
                            {
                                _messages.RemoveRange(0, _messages.Count - MessageLimit);
                            }
                            HttpContext.Current.Items[Diagnostics.ContextID] = pageTrace;
                        }
                        return (HandlerPageTraceInfo)HttpContext.Current.Items[Diagnostics.ContextID];
                    }
                }
                return null;
            }
        }
 
        /// <summary>
        /// Gets a value indicating whether this instance is trace enabled.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is trace enabled; otherwise, <c>false</c>.
        /// </value>
        internal static bool IsTraceEnabled
        {
            get
            {
                return HttpContext.Current != null && HttpContext.Current.Trace.IsEnabled;
            }
        }
 
        /// <summary>
        /// Gets the messages collection.
        /// </summary>
        /// <value>The messages.</value>
        internal static ReadOnlyCollection<HandlerPageTraceInfo> Messages
        {
            get
            {
                List<HandlerPageTraceInfo> result;
                lock (_lockObject)
                {
                    result = new List<HandlerPageTraceInfo>(_messages);
                }
                return result.AsReadOnly();
            }
        }
    }
 
    #endregion //Diagnostics class
}