#region LGPL License
/*
MagicAjax.NET Framework
Copyright (C) 2005 MagicAjax Project Team
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#endregion
using System;
using System.Collections;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace MagicAjax
{
/// <summary>
/// Helper methods intented to be used internally be the framework.
/// </summary>
internal class Util
{
public const string ScriptPattern = "<script.[^>]*>";
public const string FormElementPattern = @"<(?<tag>input|textarea|select)\s+(?<attribute>(?<attrname>[-\w]+)=(""(?<attrvalue>.*?)""\s*|'(?<attrvalue>.*?)'\s*|(?<attrvalue>[-\w]+)\s*))*.*?(/>|>((?<inner>.*?)</\k'tag'>)?)";
public const string StyleElementPattern = @"<(?<tag>style|link)\s*(?<attribute>(?<attrname>[-\w]+)=((""|')(?<attrvalue>.*?)(""|')\s*|(?<attrvalue>[-\w]+)\s*))*.*?(/>|>(?<inner>.*?)</\k'tag'>)";
#region Global Regular Expressions
private static RegexOptions _options = RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture;
public static Regex FormElementRegEx = new Regex(Util.FormElementPattern, _options);
public static Regex FormElementOptionsRegEx = new Regex("<option\\s((?<attrname>[-\\w]+)=\"(?<attrvalue>.*?)\"\\s?)*\\s*>.*?</option>", _options);
public static Regex FormElementOptionSelectedRegEx = new Regex(@"<option.*?(?<selected>selected=(""|'|)selected(""|'|)(\s+|(?=>))).*?>", _options);
public static Regex ScriptPatternRegEx = new Regex(Util.ScriptPattern, RegexOptions.IgnoreCase);
public static Regex ScriptTagsRegEx = new Regex("<script\\s((?<attrname>[-\\w]+)=[\"'](?<attrvalue>.*?)[\"']\\s?)*\\s*>(?<script>.*?)</script>", _options);
public static Regex HeaderStyleTagsRegEx = new Regex(StyleElementPattern, _options);
#endregion
/// <summary>
/// Gets the ClientID of the given Page's Form.
/// </summary>
/// <param name="page"></param>
/// <returns></returns>
public static string GetPageFormID(Page page)
{
string id = null;
#if NET_2_0
if (page.Form != null)
id = page.Form.ClientID;
#else
foreach (Control con in page.Controls)
{
if (con is HtmlForm)
{
id = con.ClientID;
break;
}
}
#endif
return id;
}
/// <summary>
/// Add 'defer' attribute to script tags excluding external script files.
/// </summary>
/// <remarks>
/// IE has a weird bug and when document.write is called, it
/// executes the scripts of the html BEFORE the external script files.
/// So add 'defer' attribute to the scripts so that external script files
/// are executed first.
/// </remarks>
/// <param name="html"></param>
/// <returns></returns>
public static string AddDeferToScriptTags (string html)
{
StringBuilder sb = new StringBuilder(html);
Regex regEx = Util.ScriptPatternRegEx;
MatchCollection scriptMatches = regEx.Matches(html);
//MatchCollection scriptMatches = Regex.Matches(html, ScriptPattern, RegexOptions.IgnoreCase);
int diff = 0;
for (int i=0; i < scriptMatches.Count; i++)
{
Match match = scriptMatches[i];
string tag = match.Value.ToLower(System.Globalization.CultureInfo.InvariantCulture);
if (tag.IndexOf(" src=") == -1)
{
// It's script block, add 'defer' attribute
tag = tag.Insert(tag.Length - 1, " defer");
}
if ( tag.Length != match.Value.Length )
{
int sbIndex = match.Index + diff;
sb.Replace(match.Value, tag, sbIndex, match.Value.Length);
diff += tag.Length - match.Value.Length;
}
}
return sb.ToString();
}
/// <summary>
/// Create a fingerprint string of an input html
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string GetFingerprint(string input)
{
input = GetHtmlWithClearedFormValues(input);
MagicAjax.Configuration.OutputCompareMode compareMode = MagicAjaxContext.Current.Configuration.CompareMode;
switch (compareMode)
{
case MagicAjax.Configuration.OutputCompareMode.FullHtml:
return input;
case MagicAjax.Configuration.OutputCompareMode.HashCode:
return input.GetHashCode().ToString("X2");
case MagicAjax.Configuration.OutputCompareMode.MD5:
byte[] inputBytes = UnicodeEncoding.Default.GetBytes(input);
byte[] hashedBytes = new MD5CryptoServiceProvider().ComputeHash(inputBytes);
return Convert.ToBase64String(hashedBytes);
default:
return input;
}
}
/// <summary>
/// Parses all form input controls from the html, and clears their values.
/// The return html is used to get a fingerprint that is not dependent on the
/// actual value of a form input control.
/// </summary>
/// <param name="html"></param>
/// <returns></returns>
public static string GetHtmlWithClearedFormValues (string html)
{
StringBuilder strbuild = new StringBuilder(html);
Regex regEx = Util.FormElementRegEx;
MatchCollection matches = regEx.Matches(html);
int diff = 0;
for (int i=0; i<matches.Count; i++)
{
Match match = matches[i];
CaptureCollection attributes = match.Groups["attribute"].Captures;
CaptureCollection attrnames = match.Groups["attrname"].Captures;
CaptureCollection attrvalues = match.Groups["attrvalue"].Captures;
Hashtable attrCaptures = new Hashtable(attributes.Count);
Hashtable attrNameValues = new Hashtable(attributes.Count);
for (int j=0; j < attributes.Count; j++)
{
string attrname = attrnames[j].Value.ToLower(System.Globalization.CultureInfo.InvariantCulture);
attrNameValues[attrname] = attrvalues[j].Value;
attrCaptures[attrname] = attributes[j];
}
// If the form element has the MagicAjax 'ExcludeFromPost' attribute
// set to 'true', ignore it. We will need its value for the fingerprint.
if ( attrNameValues.ContainsKey("excludefrompost")
&& (attrNameValues["excludefrompost"] as String).ToLower(System.Globalization.CultureInfo.InvariantCulture) == "true")
continue;
string tag = match.Groups["tag"].Value.ToLower(System.Globalization.CultureInfo.InvariantCulture);
string name = (string)attrNameValues["name"];
string clientID = (string)attrNameValues["id"];
if (name == null || clientID == null)
continue;
switch (tag)
{
#region <input> tags
case "input":
string type = (string)attrNameValues["type"];
if (type != null)
{
switch (type)
{
case "text":
if (attrNameValues.ContainsKey("value"))
{
Capture attr = attrCaptures["value"] as Capture;
int sbIndex = attr.Index - diff;
strbuild.Remove (sbIndex, attr.Length);
diff += attr.Length;
}
break;
case "checkbox":
case "radio":
if (attrNameValues.ContainsKey("checked"))
{
Capture attr = attrCaptures["checked"] as Capture;
int sbIndex = attr.Index - diff;
strbuild.Remove (sbIndex, attr.Length);
diff += attr.Length;
}
break;
}
}
break;
#endregion
#region <textarea> tags
case "textarea":
Group inner = match.Groups["inner"];
if (inner.Success)
{
int sbIndex = inner.Index - diff;
strbuild.Remove (sbIndex, inner.Length);
diff += inner.Length;
}
break;
#endregion
#region <select> tags
case "select":
Group selInner = match.Groups["inner"];
Regex regExOpt = Util.FormElementOptionSelectedRegEx;
//now remove the 'selected' attributes within this <select> tag
MatchCollection matchesSelected = regExOpt.Matches(selInner.Value);
for (int j = 0; j < matchesSelected.Count; j++)
{
Group selected = matchesSelected[j].Groups["selected"];
int sbIndex = selInner.Index + selected.Index - diff;
strbuild.Remove (sbIndex, selected.Length);
diff += selected.Length;
}
break;
#endregion
}
}
return strbuild.ToString();
}
/// <summary>
/// Resolves relative url's (starting with "~").
/// Use this when Control.ResolveUrl() is not available.
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public static string ResolveUrl(string url)
{
if (url != null && url.StartsWith("~") && HttpContext.Current != null)
{
string appPath = HttpContext.Current.Request.ApplicationPath.Length > 1 ? HttpContext.Current.Request.ApplicationPath : string.Empty;
url = appPath + url.Substring(1);
}
return url;
}
/// <summary>
/// Call a private method from the given object.
/// </summary>
/// <param name="instance"></param>
/// <param name="type"></param>
/// <param name="methodName"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public static object CallPrivateMethod(object instance, Type type, string methodName, params object[] parameters)
{
return type.GetMethod(methodName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, GetTypesOfParameters(parameters), null).Invoke(instance, parameters);
}
public static void SetPrivateField (object instance, Type type, string fieldName, object value)
{
type.GetField(fieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(instance, value);
}
public static object GetPrivateField (object instance, Type type, string fieldName)
{
return type.GetField(fieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(instance);
}
/// <summary>
/// Copies all fields (private and public) from the source object to
/// the destination object thereby performing shallow copy
/// </summary>
/// <param name="destination">object to copy fields on</param>
/// <param name="source">object to copy fields from</param>
public static void ForceShallowCopyOnObject(object destination, object source)
{
Type dstType = destination.GetType();
Type srcType = source.GetType();
// get all fields of source type
// to get all private fields loop over all base classes.
for (Type t = srcType; t != typeof(Object); t = t.BaseType)
{
System.Reflection.FieldInfo[] srcFields = t.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
foreach (System.Reflection.FieldInfo fld in srcFields)
{
string fldName = fld.Name;
object fldValue = fld.GetValue(source);
System.Reflection.FieldInfo dstFld = t.GetField(fldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
dstFld.SetValue(destination, fldValue);
}
}
}
/// <summary>
/// Gets all child controls of a specific type.
/// </summary>
/// <param name="parentControl">Control to search</param>
/// <param name="searchType">The type to search for</param>
/// <param name="notRecursiveType">If not null, the child controls of this type are not searched</param>
/// <param name="onlyVisible">if true gets only the visible controls</param>
/// <returns></returns>
public static ArrayList GetChildControlsOfType (Control parentControl, Type searchType, Type notRecursiveType, bool onlyVisible)
{
ArrayList list = new ArrayList();
for (int i=0; i < parentControl.Controls.Count; i++)
{
Control con = parentControl.Controls[i];
if (onlyVisible && !con.Visible)
continue;
if ( searchType.IsInstanceOfType(con) )
list.Add (con);
if ( notRecursiveType == null || !notRecursiveType.IsInstanceOfType(con) )
list.AddRange (GetChildControlsOfType(con, searchType, notRecursiveType, onlyVisible));
}
return list;
}
/// <summary>
/// Returns an array of types from the input object array
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
private static Type[] GetTypesOfParameters(object[] parameters)
{
if (parameters != null)
{
Type[] types = new Type[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
types[i] = parameters[i].GetType();
}
return types;
}
return null;
}
private Util()
{
}
}
}