e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
593 lines
22 KiB
C#
593 lines
22 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="BrowserDefinition.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Web.Configuration {
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Specialized;
|
|
using System.Configuration;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Web.Compilation;
|
|
using System.Web.UI;
|
|
using System.Web.Util;
|
|
using System.Xml;
|
|
using System.Globalization;
|
|
|
|
//
|
|
//
|
|
// <browsers>
|
|
// <browser id="XXX" parentID="YYY">
|
|
// <identification>
|
|
// <userAgent match="xxx" />
|
|
// <header name="HTTP_X_JPHONE_DISPLAY" match="xxx" />
|
|
// <capability name="majorVersion" match="^6$" />
|
|
// </identification>
|
|
// <capture>
|
|
// <header name="HTTP_X_UP_DEVCAP_NUMSOFTKEYS" match="?'softkeys'\d+)" />
|
|
// </capture>
|
|
// <capabilities>
|
|
// <mobileDeviceManufacturer>OpenWave</mobileDeviceManufacturer>
|
|
// <numberOfSoftKeys>$(softkeys)</numberOfSoftKeys>
|
|
// </capabilities>
|
|
// <controlAdapters>
|
|
// <adapter controlType="System.Web.UI.WebControls.Image"
|
|
// adapterType="System.Web.UI.WebControls.Adapters.Html32ImageAdapter" />
|
|
// </controlAdapters>
|
|
// </browser>
|
|
// </browsers>
|
|
|
|
internal class BrowserDefinition {
|
|
|
|
internal static string MakeValidTypeNameFromString(string s) {
|
|
if (s == null) {
|
|
return s;
|
|
}
|
|
|
|
s = s.ToLower(CultureInfo.InvariantCulture);
|
|
StringBuilder sb = new StringBuilder();
|
|
for (int i=0; i<s.Length; i++) {
|
|
// To be CLS-compliant (CS3008), public method name cannot starts with _{digit}
|
|
if (i == 0) {
|
|
if(Char.IsDigit(s[0])) {
|
|
sb.Append("N");
|
|
}
|
|
else if(Char.IsLetter(s[0])) {
|
|
sb.Append(s.Substring(0, 1).ToUpper(CultureInfo.InvariantCulture));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (Char.IsLetterOrDigit(s[i]) || s[i] == '_') {
|
|
sb.Append(s[i]);
|
|
}
|
|
else {
|
|
//
|
|
sb.Append('A');
|
|
}
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
// _idHeaderChecks are the header name and match string for <identification>
|
|
private ArrayList _idHeaderChecks;
|
|
//_idCapabilityChecks are the capability name and match string for <identification>
|
|
private ArrayList _idCapabilityChecks;
|
|
//_captureHeaderChecks are the header name and match string for <capture>
|
|
private ArrayList _captureHeaderChecks;
|
|
//_captureCapabilityChecks are the capability name and match string for <capture>
|
|
private ArrayList _captureCapabilityChecks;
|
|
private AdapterDictionary _adapters;
|
|
private string _id;
|
|
private string _parentID;
|
|
private string _name;
|
|
private string _parentName;
|
|
private NameValueCollection _capabilities;
|
|
private BrowserDefinitionCollection _browsers;
|
|
private BrowserDefinitionCollection _gateways;
|
|
private BrowserDefinitionCollection _refBrowsers;
|
|
private BrowserDefinitionCollection _refGateways;
|
|
private XmlNode _node;
|
|
private bool _isRefID = false;
|
|
private bool _isDeviceNode;
|
|
private bool _isDefaultBrowser;
|
|
private string _htmlTextWriterString;
|
|
private int _depth = 0;
|
|
|
|
internal BrowserDefinition(XmlNode node) : this(node, false) {
|
|
}
|
|
|
|
internal BrowserDefinition(XmlNode node, bool isDefaultBrowser) {
|
|
if (node == null)
|
|
throw new ArgumentNullException("node");
|
|
|
|
_capabilities = new NameValueCollection();
|
|
_idHeaderChecks = new ArrayList();
|
|
_idCapabilityChecks = new ArrayList();
|
|
_captureHeaderChecks = new ArrayList();
|
|
_captureCapabilityChecks = new ArrayList();
|
|
_adapters = new AdapterDictionary();
|
|
_browsers = new BrowserDefinitionCollection();
|
|
_gateways = new BrowserDefinitionCollection();
|
|
_refBrowsers = new BrowserDefinitionCollection();
|
|
_refGateways = new BrowserDefinitionCollection();
|
|
_node = node;
|
|
_isDefaultBrowser = isDefaultBrowser;
|
|
|
|
string refID = null;
|
|
|
|
HandlerBase.GetAndRemoveNonEmptyStringAttribute(node, "id", ref _id);
|
|
HandlerBase.GetAndRemoveNonEmptyStringAttribute(node, "refID", ref refID);
|
|
|
|
if((refID != null) && (_id != null)) {
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Browser_mutually_exclusive_attributes, "id", "refID"), node);
|
|
}
|
|
|
|
if (_id != null) {
|
|
if (!System.CodeDom.Compiler.CodeGenerator.IsValidLanguageIndependentIdentifier(_id)) {
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Browser_InvalidID, "id", _id), node);
|
|
}
|
|
}
|
|
else {
|
|
if (refID == null) {
|
|
if (this is GatewayDefinition) {
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Browser_attributes_required, "gateway", "refID", "id"), node);
|
|
}
|
|
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Browser_attributes_required, "browser", "refID", "id"), node);
|
|
}
|
|
else {
|
|
if (!System.CodeDom.Compiler.CodeGenerator.IsValidLanguageIndependentIdentifier(refID)) {
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Browser_InvalidID, "refID", refID), node);
|
|
}
|
|
}
|
|
|
|
_parentID = refID;
|
|
_isRefID = true;
|
|
_id = refID;
|
|
|
|
if (this is GatewayDefinition) {
|
|
_name = "refgatewayid$";
|
|
}
|
|
else {
|
|
_name = "refbrowserid$";
|
|
}
|
|
|
|
String parentID = null;
|
|
HandlerBase.GetAndRemoveNonEmptyStringAttribute(node, "parentID", ref parentID);
|
|
if ((parentID != null) && (parentID.Length != 0)) {
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Browser_mutually_exclusive_attributes, "parentID", "refID"), node);
|
|
}
|
|
}
|
|
|
|
_name = MakeValidTypeNameFromString(_id + _name);
|
|
|
|
if(!_isRefID) {
|
|
// Not a default browser definition
|
|
if (!("Default".Equals(_id))) {
|
|
HandlerBase.GetAndRemoveNonEmptyStringAttribute(node, "parentID", ref _parentID);
|
|
}
|
|
// Make sure parentID is not specified on default browser
|
|
else {
|
|
HandlerBase.GetAndRemoveNonEmptyStringAttribute(node, "parentID", ref _parentID);
|
|
if (_parentID != null)
|
|
throw new ConfigurationErrorsException(
|
|
SR.GetString(SR.Browser_parentID_applied_to_default), node);
|
|
}
|
|
}
|
|
|
|
_parentName = MakeValidTypeNameFromString(_parentID);
|
|
|
|
if(_id.IndexOf(" ", StringComparison.Ordinal) != -1) {
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Space_attribute, "id " + _id), node);
|
|
}
|
|
|
|
foreach(XmlNode child in node.ChildNodes) {
|
|
if(child.NodeType != XmlNodeType.Element) {
|
|
continue;
|
|
}
|
|
|
|
switch (child.Name) {
|
|
case "identification":
|
|
// refID nodes do not allow <identification>
|
|
if (_isRefID) {
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Browser_refid_prohibits_identification), node);
|
|
}
|
|
|
|
this.ProcessIdentificationNode(child, BrowserCapsElementType.Identification);
|
|
break;
|
|
|
|
case "capture":
|
|
this.ProcessCaptureNode(child, BrowserCapsElementType.Capture);
|
|
break;
|
|
|
|
case "capabilities":
|
|
this.ProcessCapabilitiesNode(child);
|
|
break;
|
|
|
|
case "controlAdapters":
|
|
this.ProcessControlAdaptersNode(child);
|
|
break;
|
|
|
|
case "sampleHeaders":
|
|
break;
|
|
|
|
default:
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Browser_invalid_element, child.Name), node);
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsDefaultBrowser {
|
|
get {
|
|
return _isDefaultBrowser;
|
|
}
|
|
}
|
|
|
|
public BrowserDefinitionCollection Browsers {
|
|
get {
|
|
return _browsers;
|
|
}
|
|
}
|
|
|
|
public BrowserDefinitionCollection RefBrowsers {
|
|
get {
|
|
return _refBrowsers;
|
|
}
|
|
}
|
|
|
|
public BrowserDefinitionCollection RefGateways {
|
|
get {
|
|
return _refGateways;
|
|
}
|
|
}
|
|
|
|
public BrowserDefinitionCollection Gateways {
|
|
get {
|
|
return _gateways;
|
|
}
|
|
}
|
|
|
|
public string ID {
|
|
get {
|
|
return _id;
|
|
}
|
|
}
|
|
|
|
public string Name {
|
|
get {
|
|
return _name;
|
|
}
|
|
}
|
|
|
|
public string ParentName {
|
|
get {
|
|
return _parentName;
|
|
}
|
|
}
|
|
|
|
// Indicate whether this node represents a real device, ie. all ancestor nodes are browser deinitions.
|
|
internal bool IsDeviceNode {
|
|
get {
|
|
return _isDeviceNode;
|
|
}
|
|
set {
|
|
_isDeviceNode = value;
|
|
}
|
|
}
|
|
|
|
internal int Depth {
|
|
get {
|
|
return _depth;
|
|
}
|
|
set {
|
|
_depth = value;
|
|
}
|
|
}
|
|
|
|
public string ParentID {
|
|
get {
|
|
return _parentID;
|
|
}
|
|
}
|
|
|
|
internal bool IsRefID {
|
|
get {
|
|
return _isRefID;
|
|
}
|
|
}
|
|
|
|
public NameValueCollection Capabilities {
|
|
get {
|
|
return _capabilities;
|
|
}
|
|
}
|
|
|
|
public ArrayList IdHeaderChecks {
|
|
get {
|
|
return _idHeaderChecks;
|
|
}
|
|
}
|
|
|
|
public ArrayList CaptureHeaderChecks {
|
|
get {
|
|
return _captureHeaderChecks;
|
|
}
|
|
}
|
|
|
|
public ArrayList IdCapabilityChecks {
|
|
get {
|
|
return _idCapabilityChecks;
|
|
}
|
|
}
|
|
|
|
public ArrayList CaptureCapabilityChecks {
|
|
get {
|
|
return _captureCapabilityChecks;
|
|
}
|
|
}
|
|
|
|
public AdapterDictionary Adapters {
|
|
get {
|
|
return _adapters;
|
|
}
|
|
}
|
|
|
|
internal XmlNode XmlNode {
|
|
get {
|
|
return _node;
|
|
}
|
|
}
|
|
|
|
public string HtmlTextWriterString {
|
|
get {
|
|
return _htmlTextWriterString;
|
|
}
|
|
}
|
|
|
|
private void DisallowNonMatchAttribute(XmlNode node) {
|
|
string check = null;
|
|
HandlerBase.GetAndRemoveStringAttribute(node, "nonMatch", ref check);
|
|
if(check != null) {
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Browser_mutually_exclusive_attributes, "match", "nonMatch"), node);
|
|
}
|
|
}
|
|
|
|
private void HandleMissingMatchAndNonMatchError(XmlNode node) {
|
|
throw new ConfigurationErrorsException(
|
|
SR.GetString(SR.Missing_required_attributes, "match", "nonMatch", node.Name),
|
|
node);
|
|
}
|
|
|
|
/*
|
|
/*
|
|
<identification>
|
|
<userAgent match="xxx" />
|
|
<header name="HTTP_X_JPHONE_DISPLAY" match="xxx" />
|
|
<capability name="majorVersion" match="^6$" />
|
|
</identification>
|
|
<capture>
|
|
<header name="HTTP_X_UP_DEVCAP_NUMSOFTKEYS" match="?'softkeys'\d+)" />
|
|
</capture>
|
|
*/
|
|
internal void ProcessIdentificationNode(XmlNode node, BrowserCapsElementType elementType) {
|
|
string match = null;
|
|
string header = null;
|
|
bool nonMatch;
|
|
bool emptyIdentification = true;
|
|
|
|
foreach(XmlNode child in node.ChildNodes) {
|
|
match = String.Empty;
|
|
nonMatch = false;
|
|
if(child.NodeType != XmlNodeType.Element) {
|
|
continue;
|
|
}
|
|
|
|
switch (child.Name) {
|
|
case "userAgent":
|
|
emptyIdentification = false;
|
|
|
|
//match the user agent
|
|
HandlerBase.GetAndRemoveNonEmptyStringAttribute(child, "match", ref match);
|
|
if (String.IsNullOrEmpty(match)) {
|
|
HandlerBase.GetAndRemoveNonEmptyStringAttribute(child, "nonMatch", ref match);
|
|
|
|
if (String.IsNullOrEmpty(match)) {
|
|
HandleMissingMatchAndNonMatchError(child);
|
|
}
|
|
|
|
nonMatch = true;
|
|
}
|
|
_idHeaderChecks.Add(new CheckPair("User-Agent", match, nonMatch));
|
|
if (nonMatch == false) {
|
|
DisallowNonMatchAttribute(child);
|
|
}
|
|
|
|
break;
|
|
|
|
case "header":
|
|
emptyIdentification = false;
|
|
|
|
//match some arbitrary header
|
|
HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "name", ref header);
|
|
HandlerBase.GetAndRemoveNonEmptyStringAttribute(child, "match", ref match);
|
|
if (String.IsNullOrEmpty(match)) {
|
|
HandlerBase.GetAndRemoveNonEmptyStringAttribute(child, "nonMatch", ref match);
|
|
|
|
if (String.IsNullOrEmpty(match)) {
|
|
HandleMissingMatchAndNonMatchError(child);
|
|
}
|
|
|
|
nonMatch = true;
|
|
}
|
|
_idHeaderChecks.Add(new CheckPair(header, match, nonMatch));
|
|
if (nonMatch == false) {
|
|
DisallowNonMatchAttribute(child);
|
|
}
|
|
break;
|
|
|
|
case "capability":
|
|
emptyIdentification = false;
|
|
|
|
//match against an already set capability
|
|
HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "name", ref header);
|
|
HandlerBase.GetAndRemoveNonEmptyStringAttribute(child, "match", ref match);
|
|
if (String.IsNullOrEmpty(match)) {
|
|
HandlerBase.GetAndRemoveNonEmptyStringAttribute(child, "nonMatch", ref match);
|
|
|
|
if (String.IsNullOrEmpty(match)) {
|
|
HandleMissingMatchAndNonMatchError(child);
|
|
}
|
|
|
|
nonMatch = true;
|
|
}
|
|
_idCapabilityChecks.Add(new CheckPair(header, match, nonMatch));
|
|
//verify that match and nonMatch are not both specified
|
|
if (nonMatch == false) {
|
|
DisallowNonMatchAttribute(child);
|
|
}
|
|
break;
|
|
default:
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Config_invalid_element, child.ToString()), child);
|
|
}
|
|
}
|
|
|
|
if (emptyIdentification) {
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Browser_empty_identification), node);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
internal void ProcessCaptureNode(XmlNode node, BrowserCapsElementType elementType) {
|
|
string match = null;
|
|
string header = null;
|
|
foreach(XmlNode child in node.ChildNodes) {
|
|
if(child.NodeType != XmlNodeType.Element) {
|
|
continue;
|
|
}
|
|
switch(child.Name) {
|
|
case "userAgent":
|
|
//match the user agent
|
|
HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "match", ref match);
|
|
_captureHeaderChecks.Add(new CheckPair("User-Agent", match));
|
|
break;
|
|
case "header":
|
|
//match some arbitrary header
|
|
HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "name", ref header);
|
|
HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "match", ref match);
|
|
_captureHeaderChecks.Add(new CheckPair(header, match));
|
|
break;
|
|
case "capability":
|
|
//match against an already set capability
|
|
HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "name", ref header);
|
|
HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "match", ref match);
|
|
_captureCapabilityChecks.Add(new CheckPair(header, match));
|
|
break;
|
|
default:
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Config_invalid_element, child.ToString()), child);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
<capabilities>
|
|
<capability name="mobileDeviceManufacturer" value="OpenWave"</capability>
|
|
<capability name="numberOfSoftKeys" value="$(softkeys)"</capability>
|
|
</capabilities>
|
|
*/
|
|
|
|
internal void ProcessCapabilitiesNode(XmlNode node) {
|
|
foreach(XmlNode child in node.ChildNodes) {
|
|
if(child.NodeType != XmlNodeType.Element) {
|
|
continue;
|
|
}
|
|
if (child.Name != "capability") {
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Config_base_unrecognized_element), child);
|
|
}
|
|
string capabilityName = null;
|
|
string capabilityValue = null;
|
|
HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "name", ref capabilityName);
|
|
HandlerBase.GetAndRemoveRequiredStringAttribute(child, "value", ref capabilityValue);
|
|
_capabilities[capabilityName] = capabilityValue;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
<controlAdapters>
|
|
<adapter controlType="System.Web.UI.WebControls.Image"
|
|
adapterType="System.Web.UI.WebControls.Adapters.Html32ImageAdapter" />
|
|
</controlAdapters>
|
|
*/
|
|
internal void ProcessControlAdaptersNode(XmlNode node) {
|
|
HandlerBase.GetAndRemoveStringAttribute(node, "markupTextWriterType", ref _htmlTextWriterString);
|
|
foreach(XmlNode child in node.ChildNodes) {
|
|
if(child.NodeType != XmlNodeType.Element) {
|
|
continue;
|
|
}
|
|
if(child.Name != "adapter") {
|
|
throw new ConfigurationErrorsException(SR.GetString(SR.Config_base_unrecognized_element), child);
|
|
}
|
|
XmlAttributeCollection nodeAttributes = child.Attributes;
|
|
string controlString = null;
|
|
string adapterString = null;
|
|
HandlerBase.GetAndRemoveRequiredNonEmptyStringAttribute(child, "controlType", ref controlString);
|
|
HandlerBase.GetAndRemoveRequiredStringAttribute(child, "adapterType", ref adapterString);
|
|
|
|
Type type = CheckType(controlString, typeof(Control), child);
|
|
|
|
// Normalize control type name
|
|
controlString = type.AssemblyQualifiedName;
|
|
|
|
if (!String.IsNullOrEmpty(adapterString)) {
|
|
CheckType(adapterString, typeof(System.Web.UI.Adapters.ControlAdapter), child);
|
|
}
|
|
|
|
_adapters[controlString] = adapterString;
|
|
}
|
|
return;
|
|
}
|
|
|
|
private static Type CheckType(string typeName, Type baseType, XmlNode child) {
|
|
// Use BuildManager to verify control types.
|
|
// Note for machine level browser files, this will only check assemblies in GAC.
|
|
Type type = ConfigUtil.GetType(typeName, child, true /*ignoreCase*/);
|
|
|
|
if (!baseType.IsAssignableFrom(type)) {
|
|
throw new ConfigurationErrorsException(
|
|
SR.GetString(SR.Type_doesnt_inherit_from_type, typeName,
|
|
baseType.FullName), child);
|
|
}
|
|
|
|
if (!HttpRuntime.IsTypeAllowedInConfig(type)) {
|
|
throw new ConfigurationErrorsException(
|
|
SR.GetString(SR.Type_from_untrusted_assembly, typeName), child);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
internal void MergeWithDefinition(BrowserDefinition definition) {
|
|
Debug.Assert(definition.IsRefID);
|
|
|
|
// Copy the capabilities
|
|
foreach (String key in definition.Capabilities.Keys) {
|
|
this._capabilities[key] = definition.Capabilities[key];
|
|
}
|
|
|
|
// Copy the adapter definition
|
|
foreach (String key in definition.Adapters.Keys) {
|
|
this._adapters[key] = definition.Adapters[key];
|
|
}
|
|
|
|
this._htmlTextWriterString = definition.HtmlTextWriterString;
|
|
}
|
|
}
|
|
}
|