File: System\ServiceModel\Discovery\ScopeCompiler.cs
Project: ndp\cdf\src\NetFx40\System.ServiceModel.Discovery\System.ServiceModel.Discovery.csproj (System.ServiceModel.Discovery)
//----------------------------------------------------------------
// 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<Uri> scopes)
        {
            if (scopes == null || scopes.Count == 0)
            {
                return null;
            }
 
            List<string> compiledScopes = new List<string>();
            foreach (Uri scope in scopes)
            {
                Compile(scope, compiledScopes);
            }
 
            return compiledScopes.ToArray();
        }
 
        public static CompiledScopeCriteria[] CompileMatchCriteria(ICollection<Uri> scopes, Uri matchBy)
        {
            Fx.Assert(matchBy != null, "The matchBy must be non null.");
 
            if (scopes == null || scopes.Count == 0)
            {
                return null;
            }
 
            List<CompiledScopeCriteria> compiledCriterias = new List<CompiledScopeCriteria>();
            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<string> 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<string, string> attrTypeValueTable = new Dictionary<string, string>();
            List<string> attrTypeList = new List<string>();
 
            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();
        }
    }
}