//---------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------- namespace System.ServiceModel.Discovery { using System; using System.Collections.Generic; using System.Runtime; using System.Text; using SR2 = System.ServiceModel.Discovery.SR; static class ScopeCompiler { public static string[] Compile(ICollection scopes) { if (scopes == null || scopes.Count == 0) { return null; } List compiledScopes = new List(); foreach (Uri scope in scopes) { Compile(scope, compiledScopes); } return compiledScopes.ToArray(); } public static CompiledScopeCriteria[] CompileMatchCriteria(ICollection scopes, Uri matchBy) { Fx.Assert(matchBy != null, "The matchBy must be non null."); if (scopes == null || scopes.Count == 0) { return null; } List compiledCriterias = new List(); foreach (Uri scope in scopes) { compiledCriterias.Add(CompileCriteria(scope, matchBy)); } return compiledCriterias.ToArray(); } public static bool IsSupportedMatchingRule(Uri matchBy) { Fx.Assert(matchBy != null, "The matchBy must be non null."); return (matchBy.Equals(FindCriteria.ScopeMatchByPrefix) || matchBy.Equals(FindCriteria.ScopeMatchByUuid) || matchBy.Equals(FindCriteria.ScopeMatchByLdap) || matchBy.Equals(FindCriteria.ScopeMatchByExact) || matchBy.Equals(FindCriteria.ScopeMatchByNone)); } public static bool IsMatch(CompiledScopeCriteria compiledScopeMatchCriteria, string[] compiledScopes) { Fx.Assert(compiledScopeMatchCriteria != null, "The compiledScopeMatchCriteria must be non null."); Fx.Assert(compiledScopes != null, "The compiledScopes must be non null."); if (compiledScopeMatchCriteria.MatchBy == CompiledScopeCriteriaMatchBy.Exact) { for (int i = 0; i < compiledScopes.Length; i++) { if (string.CompareOrdinal(compiledScopes[i], compiledScopeMatchCriteria.CompiledScope) == 0) { return true; } } } else if (compiledScopeMatchCriteria.MatchBy == CompiledScopeCriteriaMatchBy.StartsWith) { for (int i = 0; i < compiledScopes.Length; i++) { if (compiledScopes[i].StartsWith(compiledScopeMatchCriteria.CompiledScope, StringComparison.Ordinal)) { return true; } } } return false; } static void Compile(Uri scope, List compiledScopes) { // MatchByRfc2396 can be applied to any URI compiledScopes.Add(CompileForMatchByRfc2396(scope)); // MatchByUuid can be applied to only UUIDs we treat urn:uuid:GUID same as uuid:GUID Guid guid; if (TryGetUuidGuid(scope, out guid)) { compiledScopes.Add(CompileForMatchByUuid(guid)); } // MatchByStrcmp0 can be applied to any URI compiledScopes.Add(CompileForMatchByStrcmp0(scope)); // MatchByLdap can be applied to only LDAP URI if (string.Compare(scope.Scheme, "ldap", StringComparison.OrdinalIgnoreCase) == 0) { compiledScopes.Add(CompileForMatchByLdap(scope)); } } static CompiledScopeCriteria CompileCriteria(Uri scope, Uri matchBy) { string compiledScope; CompiledScopeCriteriaMatchBy compiledMatchBy; if (matchBy.Equals(FindCriteria.ScopeMatchByPrefix)) { compiledScope = CompileForMatchByRfc2396(scope); compiledMatchBy = CompiledScopeCriteriaMatchBy.StartsWith; } else if (matchBy.Equals(FindCriteria.ScopeMatchByUuid)) { Guid guid; if (!TryGetUuidGuid(scope, out guid)) { throw FxTrace.Exception.AsError(new FormatException(SR2.DiscoveryFormatInvalidScopeUuidUri(scope.ToString()))); } compiledScope = CompileForMatchByUuid(guid); compiledMatchBy = CompiledScopeCriteriaMatchBy.Exact; } else if (matchBy.Equals(FindCriteria.ScopeMatchByLdap)) { if (string.Compare(scope.Scheme, "ldap", StringComparison.OrdinalIgnoreCase) != 0) { throw FxTrace.Exception.AsError(new FormatException(SR2.DiscoveryFormatInvalidScopeLdapUri(scope.ToString()))); } compiledScope = CompileForMatchByLdap(scope); compiledMatchBy = CompiledScopeCriteriaMatchBy.StartsWith; } else if (matchBy.Equals(FindCriteria.ScopeMatchByExact)) { compiledScope = CompileForMatchByStrcmp0(scope); compiledMatchBy = CompiledScopeCriteriaMatchBy.Exact; } else { throw FxTrace.Exception.ArgumentOutOfRange("matchBy", matchBy, SR2.DiscoveryMatchingRuleNotSupported( FindCriteria.ScopeMatchByExact, FindCriteria.ScopeMatchByPrefix, FindCriteria.ScopeMatchByUuid, FindCriteria.ScopeMatchByLdap)); } return new CompiledScopeCriteria(compiledScope, compiledMatchBy); } static string CompileForMatchByRfc2396(Uri scope) { StringBuilder compiledScopeBuilder = new StringBuilder(); // Append the matching rule name, so this compiled scope can only be // matched for that particular matching rule. compiledScopeBuilder.Append("rfc2396match::"); // // Rule: Using a case-insensitive comparison, The scheme [RFC 2396] // of S1 and S2 is the same and // string scheme = scope.GetComponents(UriComponents.Scheme, UriFormat.UriEscaped); if (scheme != null) { scheme = scheme.ToUpperInvariant(); } else { scheme = string.Empty; } compiledScopeBuilder.Append(scheme); compiledScopeBuilder.Append(":"); // // Rule: Using a case-insensitive comparison, The authority of S1 // and S2 is the same and // string authority = scope.GetComponents(UriComponents.StrongAuthority, UriFormat.UriEscaped); if (authority != null) { authority = authority.ToUpperInvariant(); } else { authority = string.Empty; } compiledScopeBuilder.Append(authority); compiledScopeBuilder.Append(":"); // // Rule: The path_segments of S1 is a segment-wise (not string) // prefix of the path_segments of S2 and Neither S1 nor S2 // contain the "." segment or the ".." segment. All other // components (e.g., query and fragment) are explicitly // excluded from comparison. S1 and S2 MUST be canonicalized // (e.g., unescaping escaped characters) before using this // matching rule. foreach (string segment in scope.Segments) { compiledScopeBuilder.Append(ProcessUriSegment(segment)); } return compiledScopeBuilder.ToString(); } static string ProcessUriSegment(string segment) { // ignore the segment parameters, if any int index = segment.IndexOf(';'); if (index != -1) { segment = segment.Substring(0, index); } // prevent the comparision of partial segments // Note: this matching rule does NOT test whether the string // representation of S1 is a prefix of the string representation // of S2. For example, "http://example.com/abc" matches // "http://example.com/abc/def" using this rule but // "http://example.com/a" does not. if (!segment.EndsWith("/", StringComparison.Ordinal)) { segment = segment + "/"; } return segment; } static bool TryGetUuidGuid(Uri scope, out Guid guid) { string guidString = null; if (string.Compare(scope.Scheme, "uuid", StringComparison.OrdinalIgnoreCase) == 0) { guidString = scope.GetComponents(UriComponents.Path, UriFormat.UriEscaped); } else if (string.Compare(scope.Scheme, "urn", StringComparison.OrdinalIgnoreCase) == 0) { string scopeString = scope.ToString(); if (string.Compare(scopeString, 4, "uuid:", 0, 5, StringComparison.OrdinalIgnoreCase) == 0) { guidString = scopeString.Substring(9); } } return Fx.TryCreateGuid(guidString, out guid); } static string CompileForMatchByUuid(Guid guid) { // Append the matching rule name, so this compiled scope can only be // matched for that particular matching rule. // // Rule: Using a case-insensitive comparison, the scheme of S1 and // S2 is "uuid" and each of the unsigned integer fields [UUID] // in S1 is equal to the corresponding field in S2, or // equivalently, the 128 bits of the in-memory representation // of S1 and S2 are the same 128 bit unsigned integer. return "uuidmatch::" + guid.ToString(); } static string CompileForMatchByStrcmp0(Uri scope) { // // Rule: Using a case-sensitive comparison, the string // representation of S1 and S2 is the same. // return "strcmp0match::" + scope.ToString(); } static string CompileForMatchByLdap(Uri scope) { StringBuilder compiledScopeBuilder = new StringBuilder(); // Append the matching rule name, so this compiled scope can only be // matched for that particular matching rule. compiledScopeBuilder.Append("ldapmatch::"); // // Rule: Using a case-insensitive comparison, the scheme of S1 // and S2 is "ldap" and compiledScopeBuilder.Append("ldap:"); // // Rule: and the hostport [RFC 2255] of S1 and S2 is the // same and // string hostport = scope.GetComponents(UriComponents.HostAndPort, UriFormat.UriEscaped); if (hostport != null) { hostport = hostport.ToUpperInvariant(); } else { hostport = string.Empty; } compiledScopeBuilder.Append(hostport); compiledScopeBuilder.Append(":"); // // Rule: and the RDNSequence [RFC 2253] of the dn of S1 is a // prefix of the RDNSequence of the dn of S2, where comparison // does not support the variants in an RDNSequence described // in Section 4 of RFC 2253 [RFC 2253]. // // get the ldap DN string. string dn = scope.GetComponents(UriComponents.Path, UriFormat.Unescaped); // parse the RDNs in order from DN compiledScopeBuilder.Append(ParseLdapRDNSequence(dn)); return compiledScopeBuilder.ToString(); } static string ParseLdapRDNSequence(string dn) { // Assuming the conversion of DN to string as per Section 2 RFC2253 // ignoring the variations described in section 4. StringBuilder rdnSequenceBuilder = new StringBuilder(); string[] tokens = dn.Split(','); StringBuilder rdnBuilder = new StringBuilder(); foreach (string token in tokens) { if (string.IsNullOrEmpty(token.Trim())) { continue; } if (token.EndsWith("\\", StringComparison.Ordinal)) { // it is part of the RDN rdnBuilder.Append(token.Substring(0, token.Length - 1)); rdnBuilder.Append(','); } else { // RDN ends here. rdnBuilder.Append(token); rdnSequenceBuilder.Insert(0, "/"); rdnSequenceBuilder.Insert(0, ParseAndSortRDNAttributes(rdnBuilder.ToString())); rdnBuilder = new StringBuilder(); } } return rdnSequenceBuilder.ToString(); } static string ParseAndSortRDNAttributes(string rdn) { // // Rule: RFC2253 Section 2: // When converting from an ASN.1 RelativeDistinguishedName // to a string, the output consists of the string encodings // of each AttributeTypeAndValue (according to 2.3), in any // order. Where there is a multi-valued RDN, the outputs // from adjoining AttributeTypeAndValues are separated by // a plus ('+' ASCII 43) character. // // since the RDN attributes can be converted to string in any order // we must make sure that the compiled form of the scope and match // criteria have the same order so that simple string prefix // comparision produces the same result of comparing the RDN // attribute values individually, we sort the attributes or RDN // based on their name. // optimize the case where there is only one attrvalue for RDN if (rdn.IndexOf('+') == -1) { return rdn; } string[] tokens = rdn.Split('+'); StringBuilder attrTypeAndValueBuilder = new StringBuilder(); Dictionary attrTypeValueTable = new Dictionary(); List attrTypeList = new List(); foreach (string token in tokens) { if (string.IsNullOrEmpty(token.Trim())) { continue; } if (token.EndsWith("\\", StringComparison.Ordinal)) { // it is part of the attribute value attrTypeAndValueBuilder.Append(token.Substring(0, token.Length - 1)); attrTypeAndValueBuilder.Append('+'); } else { // attribute value ends here. attrTypeAndValueBuilder.Append(token); // get attribute and value. string attrTypeAndValue = attrTypeAndValueBuilder.ToString(); string attrType = attrTypeAndValue; string attrValue = null; int equalIndex = attrTypeAndValue.IndexOf('='); if (equalIndex != -1) { attrType = attrTypeAndValue.Substring(0, equalIndex); attrValue = attrTypeAndValue.Substring(equalIndex + 1); } attrTypeList.Add(attrType); attrTypeValueTable.Add(attrType, attrValue); attrTypeAndValueBuilder = new StringBuilder(); } } // sort the list based on the attribute type attrTypeList.Sort(); // created the RDN from the sorted attribute values. StringBuilder rdnBuilder = new StringBuilder(); for (int i = 0; i < attrTypeList.Count - 1; i++) { rdnBuilder.Append(attrTypeList[i]); if (attrTypeValueTable[attrTypeList[i]] != null) { rdnBuilder.Append("="); rdnBuilder.Append(attrTypeValueTable[attrTypeList[i]]); } rdnBuilder.Append("+"); } if (attrTypeList.Count > 1) { rdnBuilder.Append(attrTypeList[attrTypeList.Count - 1]); if (attrTypeValueTable[attrTypeList[attrTypeList.Count - 1]] != null) { rdnBuilder.Append("="); rdnBuilder.Append(attrTypeValueTable[attrTypeList[attrTypeList.Count - 1]]); } rdnBuilder.Append("+"); } return rdnBuilder.ToString(); } } }