|
//--------------------------------------------------------------------------------------------------------------------------
// <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
}
|