The Labs \ Source Viewer \ SSCLI \ System.Security.Util \ StringExpressionSet

  1. // ==++==
  2. //
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. //
  14. // ==--==
  15. // StringExpressionSet
  16. //
  17. namespace System.Security.Util
  18. {
  19.     using System.Text;
  20.     using System;
  21.     using System.Collections;
  22.     using System.Runtime.CompilerServices;
  23.     using System.Globalization;
  24.     using System.Runtime.Versioning;
  25.    
  26.     [Serializable()]
  27.     internal class StringExpressionSet
  28.     {
  29.         protected ArrayList m_list;
  30.         protected bool m_ignoreCase;
  31.         protected string m_expressions;
  32.         protected string[] m_expressionsArray;
  33.         protected bool m_throwOnRelative;
  34.        
  35.         protected static readonly char[] m_separators = {';'};
  36.         protected static readonly char[] m_trimChars = {' '};
  37.         #if !PLATFORM_UNIX
  38.         protected static readonly char m_directorySeparator = '\\';
  39.         protected static readonly char m_alternateDirectorySeparator = '/';
  40.         #else
  41.         protected static readonly char m_directorySeparator = '/';
  42.         protected static readonly char m_alternateDirectorySeparator = '\\';
  43.         #endif // !PLATFORM_UNIX
  44.        
  45.         public StringExpressionSet() : this(true, null, false)
  46.         {
  47.         }
  48.        
  49.         public StringExpressionSet(string str) : this(true, str, false)
  50.         {
  51.         }
  52.        
  53.         public StringExpressionSet(bool ignoreCase, bool throwOnRelative) : this(ignoreCase, null, throwOnRelative)
  54.         {
  55.         }
  56.        
  57.         public StringExpressionSet(bool ignoreCase, string str, bool throwOnRelative)
  58.         {
  59.             m_list = null;
  60.             m_ignoreCase = ignoreCase;
  61.             m_throwOnRelative = throwOnRelative;
  62.             if (str == null)
  63.                 m_expressions = null;
  64.             else
  65.                 AddExpressions(str);
  66.         }
  67.        
  68.         protected virtual StringExpressionSet CreateNewEmpty()
  69.         {
  70.             return new StringExpressionSet();
  71.         }
  72.        
  73.         public virtual StringExpressionSet Copy()
  74.         {
  75.             StringExpressionSet copy = CreateNewEmpty();
  76.             if (this.m_list != null)
  77.                 copy.m_list = new ArrayList(this.m_list);
  78.             copy.m_expressions = this.m_expressions;
  79.             copy.m_ignoreCase = this.m_ignoreCase;
  80.             copy.m_throwOnRelative = this.m_throwOnRelative;
  81.             return copy;
  82.         }
  83.        
  84.         public void SetThrowOnRelative(bool throwOnRelative)
  85.         {
  86.             this.m_throwOnRelative = throwOnRelative;
  87.         }
  88.        
  89.         private static string StaticProcessWholeString(string str)
  90.         {
  91.             return str.Replace(m_alternateDirectorySeparator, m_directorySeparator);
  92.         }
  93.        
  94.         private static string StaticProcessSingleString(string str)
  95.         {
  96.             return str.Trim(m_trimChars);
  97.         }
  98.        
  99.         protected virtual string ProcessWholeString(string str)
  100.         {
  101.             return StaticProcessWholeString(str);
  102.         }
  103.        
  104.         protected virtual string ProcessSingleString(string str)
  105.         {
  106.             return StaticProcessSingleString(str);
  107.         }
  108.        
  109.         public void AddExpressions(string str)
  110.         {
  111.             if (str == null)
  112.                 throw new ArgumentNullException("str");
  113.             if (str.Length == 0)
  114.                 return;
  115.            
  116.             str = ProcessWholeString(str);
  117.            
  118.             if (m_expressions == null)
  119.                 m_expressions = str;
  120.             else
  121.                 m_expressions = m_expressions + m_separators[0] + str;
  122.            
  123.             m_expressionsArray = null;
  124.            
  125.             // We have to parse the string and compute the list here.
  126.             // The logic in this class tries to delay this parsing but
  127.             // since operations like IsSubsetOf are called during
  128.             // demand evaluation, it is not safe to delay this step
  129.             // as that would cause concurring threads to update the object
  130.             // at the same time. The CheckList operation should ideally be
  131.             // removed from this class, but for the sake of keeping the
  132.             // changes to a minimum here, we simply make sure m_list
  133.             // cannot be null by parsing m_expressions eagerly.
  134.            
  135.             string[] arystr = Split(str);
  136.            
  137.             if (m_list == null)
  138.                 m_list = new ArrayList();
  139.            
  140.             for (int index = 0; index < arystr.Length; ++index) {
  141.                 if (arystr[index] != null && !arystr[index].Equals("")) {
  142.                     string temp = ProcessSingleString(arystr[index]);
  143.                     int indexOfNull = temp.IndexOf('\0');
  144.                    
  145.                     if (indexOfNull != -1)
  146.                         temp = temp.Substring(0, indexOfNull);
  147.                    
  148.                     if (temp != null && !temp.Equals("")) {
  149.                         if (m_throwOnRelative) {
  150.                             #if !PLATFORM_UNIX
  151.                             if (!((temp.Length >= 3 && temp[1] == ':' && temp[2] == '\\' && ((temp[0] >= 'a' && temp[0] <= 'z') || (temp[0] >= 'A' && temp[0] <= 'Z'))) || (temp.Length >= 2 && temp[0] == '\\' && temp[1] == '\\')))
  152.                                 #else
  153.                                 #endif // !PLATFORM_UNIX
  154.                                 if (!(temp.Length >= 1 && temp[0] == m_directorySeparator)) {
  155.                                     throw new ArgumentException(Environment.GetResourceString("Argument_AbsolutePathRequired"));
  156.                                 }
  157.                            
  158.                             temp = CanonicalizePath(temp);
  159.                         }
  160.                        
  161.                         m_list.Add(temp);
  162.                     }
  163.                 }
  164.             }
  165.            
  166.             Reduce();
  167.         }
  168.        
  169.         public void AddExpressions(string[] str, bool checkForDuplicates, bool needFullPath)
  170.         {
  171.             AddExpressions(CreateListFromExpressions(str, needFullPath), checkForDuplicates);
  172.         }
  173.        
  174.         public void AddExpressions(ArrayList exprArrayList, bool checkForDuplicates)
  175.         {
  176.             BCLDebug.Assert(m_throwOnRelative, "This should only be called when throw on relative is set");
  177.            
  178.             m_expressionsArray = null;
  179.             m_expressions = null;
  180.            
  181.             if (m_list != null)
  182.                 m_list.AddRange(exprArrayList);
  183.             else
  184.                 m_list = new ArrayList(exprArrayList);
  185.            
  186.            
  187.             if (checkForDuplicates)
  188.                 Reduce();
  189.            
  190.         }
  191.        
  192.         static internal ArrayList CreateListFromExpressions(string[] str, bool needFullPath)
  193.         {
  194.             if (str == null) {
  195.                 throw new ArgumentNullException("str");
  196.             }
  197.             ArrayList retArrayList = new ArrayList();
  198.             for (int index = 0; index < str.Length; ++index) {
  199.                 if (str[index] == null)
  200.                     throw new ArgumentNullException("str");
  201.                
  202.                 string oneString = StaticProcessWholeString(str[index]);
  203.                
  204.                 if (oneString != null && oneString.Length != 0) {
  205.                     string temp = StaticProcessSingleString(oneString);
  206.                    
  207.                     int indexOfNull = temp.IndexOf('\0');
  208.                    
  209.                     if (indexOfNull != -1)
  210.                         temp = temp.Substring(0, indexOfNull);
  211.                    
  212.                     if (temp != null && temp.Length != 0) {
  213.                         #if !PLATFORM_UNIX
  214.                         if (!((temp.Length >= 3 && temp[1] == ':' && temp[2] == '\\' && ((temp[0] >= 'a' && temp[0] <= 'z') || (temp[0] >= 'A' && temp[0] <= 'Z'))) || (temp.Length >= 2 && temp[0] == '\\' && temp[1] == '\\')))
  215.                             #else
  216.                             #endif // !PLATFORM_UNIX
  217.                             if (!(temp.Length >= 1 && temp[0] == m_directorySeparator)) {
  218.                                 throw new ArgumentException(Environment.GetResourceString("Argument_AbsolutePathRequired"));
  219.                             }
  220.                        
  221.                         temp = CanonicalizePath(temp, needFullPath);
  222.                        
  223.                        
  224.                         retArrayList.Add(temp);
  225.                     }
  226.                 }
  227.             }
  228.            
  229.             return retArrayList;
  230.         }
  231.        
  232.         protected void CheckList()
  233.         {
  234.             if (m_list == null && m_expressions != null) {
  235.                 CreateList();
  236.             }
  237.         }
  238.        
  239.         protected string[] Split(string expressions)
  240.         {
  241.             if (m_throwOnRelative) {
  242.                 ArrayList tempList = new ArrayList();
  243.                
  244.                 string[] quoteSplit = expressions.Split('"');
  245.                
  246.                 for (int i = 0; i < quoteSplit.Length; ++i) {
  247.                     if (i % 2 == 0) {
  248.                         string[] semiSplit = quoteSplit[i].Split(';');
  249.                        
  250.                         for (int j = 0; j < semiSplit.Length; ++j) {
  251.                             if (semiSplit[j] != null && !semiSplit[j].Equals(""))
  252.                                 tempList.Add(semiSplit[j]);
  253.                         }
  254.                     }
  255.                     else {
  256.                         tempList.Add(quoteSplit[i]);
  257.                     }
  258.                 }
  259.                
  260.                 string[] finalArray = new string[tempList.Count];
  261.                
  262.                 IEnumerator enumerator = tempList.GetEnumerator();
  263.                
  264.                 int index = 0;
  265.                 while (enumerator.MoveNext()) {
  266.                     finalArray[index++] = (string)enumerator.Current;
  267.                 }
  268.                
  269.                 return finalArray;
  270.             }
  271.             else {
  272.                 return expressions.Split(m_separators);
  273.             }
  274.         }
  275.        
  276.        
  277.         protected void CreateList()
  278.         {
  279.             string[] expressionsArray = Split(m_expressions);
  280.            
  281.             m_list = new ArrayList();
  282.            
  283.             for (int index = 0; index < expressionsArray.Length; ++index) {
  284.                 if (expressionsArray[index] != null && !expressionsArray[index].Equals("")) {
  285.                     string temp = ProcessSingleString(expressionsArray[index]);
  286.                    
  287.                     int indexOfNull = temp.IndexOf('\0');
  288.                    
  289.                     if (indexOfNull != -1)
  290.                         temp = temp.Substring(0, indexOfNull);
  291.                    
  292.                     if (temp != null && !temp.Equals("")) {
  293.                         if (m_throwOnRelative) {
  294.                             #if !PLATFORM_UNIX
  295.                             if (!((temp.Length >= 3 && temp[1] == ':' && temp[2] == '\\' && ((temp[0] >= 'a' && temp[0] <= 'z') || (temp[0] >= 'A' && temp[0] <= 'Z'))) || (temp.Length >= 2 && temp[0] == '\\' && temp[1] == '\\')))
  296.                                 #else
  297.                                 #endif // !PLATFORM_UNIX
  298.                                 if (!(temp.Length >= 1 && temp[0] == m_directorySeparator)) {
  299.                                     throw new ArgumentException(Environment.GetResourceString("Argument_AbsolutePathRequired"));
  300.                                 }
  301.                            
  302.                             temp = CanonicalizePath(temp);
  303.                         }
  304.                        
  305.                         m_list.Add(temp);
  306.                     }
  307.                 }
  308.             }
  309.         }
  310.        
  311.         public bool IsEmpty()
  312.         {
  313.             if (m_list == null) {
  314.                 return m_expressions == null;
  315.             }
  316.             else {
  317.                 return m_list.Count == 0;
  318.             }
  319.         }
  320.        
  321.         public bool IsSubsetOf(StringExpressionSet ses)
  322.         {
  323.             if (this.IsEmpty())
  324.                 return true;
  325.            
  326.             if (ses == null || ses.IsEmpty())
  327.                 return false;
  328.            
  329.             CheckList();
  330.             ses.CheckList();
  331.            
  332.             for (int index = 0; index < this.m_list.Count; ++index) {
  333.                 if (!StringSubsetStringExpression((string)this.m_list[index], ses, m_ignoreCase)) {
  334.                     return false;
  335.                 }
  336.             }
  337.             return true;
  338.         }
  339.        
  340.         public bool IsSubsetOfPathDiscovery(StringExpressionSet ses)
  341.         {
  342.             if (this.IsEmpty())
  343.                 return true;
  344.            
  345.             if (ses == null || ses.IsEmpty())
  346.                 return false;
  347.            
  348.             CheckList();
  349.             ses.CheckList();
  350.            
  351.             for (int index = 0; index < this.m_list.Count; ++index) {
  352.                 if (!StringSubsetStringExpressionPathDiscovery((string)this.m_list[index], ses, m_ignoreCase)) {
  353.                     return false;
  354.                 }
  355.             }
  356.             return true;
  357.         }
  358.        
  359.        
  360.         public StringExpressionSet Union(StringExpressionSet ses)
  361.         {
  362.             // If either set is empty, the union represents a copy of the other.
  363.            
  364.             if (ses == null || ses.IsEmpty())
  365.                 return this.Copy();
  366.            
  367.             if (this.IsEmpty())
  368.                 return ses.Copy();
  369.            
  370.             CheckList();
  371.             ses.CheckList();
  372.            
  373.             // Perform the union
  374.             // note: insert smaller set into bigger set to reduce needed comparisons
  375.            
  376.             StringExpressionSet bigger = ses.m_list.Count > this.m_list.Count ? ses : this;
  377.             StringExpressionSet smaller = ses.m_list.Count <= this.m_list.Count ? ses : this;
  378.            
  379.             StringExpressionSet unionSet = bigger.Copy();
  380.            
  381.             unionSet.Reduce();
  382.            
  383.             for (int index = 0; index < smaller.m_list.Count; ++index) {
  384.                 unionSet.AddSingleExpressionNoDuplicates((string)smaller.m_list[index]);
  385.             }
  386.            
  387.             unionSet.GenerateString();
  388.            
  389.             return unionSet;
  390.         }
  391.        
  392.        
  393.         public StringExpressionSet Intersect(StringExpressionSet ses)
  394.         {
  395.             // If either set is empty, the intersection is empty
  396.            
  397.             if (this.IsEmpty() || ses == null || ses.IsEmpty())
  398.                 return CreateNewEmpty();
  399.            
  400.             CheckList();
  401.             ses.CheckList();
  402.            
  403.             // Do the intersection for real
  404.            
  405.             StringExpressionSet intersectSet = CreateNewEmpty();
  406.            
  407.             for (int this_index = 0; this_index < this.m_list.Count; ++this_index) {
  408.                 for (int ses_index = 0; ses_index < ses.m_list.Count; ++ses_index) {
  409.                     if (StringSubsetString((string)this.m_list[this_index], (string)ses.m_list[ses_index], m_ignoreCase)) {
  410.                         if (intersectSet.m_list == null) {
  411.                             intersectSet.m_list = new ArrayList();
  412.                         }
  413.                         intersectSet.AddSingleExpressionNoDuplicates((string)this.m_list[this_index]);
  414.                     }
  415.                     else if (StringSubsetString((string)ses.m_list[ses_index], (string)this.m_list[this_index], m_ignoreCase)) {
  416.                         if (intersectSet.m_list == null) {
  417.                             intersectSet.m_list = new ArrayList();
  418.                         }
  419.                         intersectSet.AddSingleExpressionNoDuplicates((string)ses.m_list[ses_index]);
  420.                     }
  421.                 }
  422.             }
  423.            
  424.             intersectSet.GenerateString();
  425.            
  426.             return intersectSet;
  427.         }
  428.        
  429.         protected void GenerateString()
  430.         {
  431.             if (m_list != null) {
  432.                 StringBuilder sb = new StringBuilder();
  433.                
  434.                 IEnumerator enumerator = this.m_list.GetEnumerator();
  435.                 bool first = true;
  436.                
  437.                 while (enumerator.MoveNext()) {
  438.                     if (!first)
  439.                         sb.Append(m_separators[0]);
  440.                     else
  441.                         first = false;
  442.                    
  443.                     string currentString = (string)enumerator.Current;
  444.                     if (currentString != null) {
  445.                         int indexOfSeparator = currentString.IndexOf(m_separators[0]);
  446.                        
  447.                         if (indexOfSeparator != -1)
  448.                             sb.Append('"');
  449.                        
  450.                         sb.Append(currentString);
  451.                        
  452.                         if (indexOfSeparator != -1)
  453.                             sb.Append('"');
  454.                     }
  455.                 }
  456.                
  457.                 m_expressions = sb.ToString();
  458.             }
  459.             else {
  460.                 m_expressions = null;
  461.             }
  462.         }
  463.        
  464.         public override string ToString()
  465.         {
  466.             CheckList();
  467.            
  468.             Reduce();
  469.            
  470.             GenerateString();
  471.            
  472.             return m_expressions;
  473.         }
  474.        
  475.         public string[] ToStringArray()
  476.         {
  477.             if (m_expressionsArray == null && m_list != null) {
  478.                 m_expressionsArray = (string[])m_list.ToArray(typeof(string));
  479.             }
  480.            
  481.             return m_expressionsArray;
  482.         }
  483.        
  484.        
  485.         //-------------------------------
  486.         // protected static helper functions
  487.         //-------------------------------
  488.        
  489.        
  490.         protected bool StringSubsetStringExpression(string left, StringExpressionSet right, bool ignoreCase)
  491.         {
  492.             for (int index = 0; index < right.m_list.Count; ++index) {
  493.                 if (StringSubsetString(left, (string)right.m_list[index], ignoreCase)) {
  494.                     return true;
  495.                 }
  496.             }
  497.             return false;
  498.         }
  499.        
  500.         protected static bool StringSubsetStringExpressionPathDiscovery(string left, StringExpressionSet right, bool ignoreCase)
  501.         {
  502.             for (int index = 0; index < right.m_list.Count; ++index) {
  503.                 if (StringSubsetStringPathDiscovery(left, (string)right.m_list[index], ignoreCase)) {
  504.                     return true;
  505.                 }
  506.             }
  507.             return false;
  508.         }
  509.        
  510.        
  511.         protected virtual bool StringSubsetString(string left, string right, bool ignoreCase)
  512.         {
  513.             StringComparison strComp = (ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
  514.             if (right == null || left == null || right.Length == 0 || left.Length == 0 || right.Length > left.Length) {
  515.                 return false;
  516.             }
  517.             else if (right.Length == left.Length) {
  518.                 // if they are equal in length, just do a normal compare
  519.                 return String.Compare(right, left, strComp) == 0;
  520.             }
  521.             else if (left.Length - right.Length == 1 && left[left.Length - 1] == m_directorySeparator) {
  522.                 return String.Compare(left, 0, right, 0, right.Length, strComp) == 0;
  523.             }
  524.             else if (right[right.Length - 1] == m_directorySeparator) {
  525.                 // right is definitely a directory, just do a substring compare
  526.                 return String.Compare(right, 0, left, 0, right.Length, strComp) == 0;
  527.             }
  528.             else if (left[right.Length] == m_directorySeparator) {
  529.                 // left is hinting at being a subdirectory on right, do substring compare to make find out
  530.                 return String.Compare(right, 0, left, 0, right.Length, strComp) == 0;
  531.             }
  532.             else {
  533.                 return false;
  534.             }
  535.         }
  536.        
  537.         protected static bool StringSubsetStringPathDiscovery(string left, string right, bool ignoreCase)
  538.         {
  539.             StringComparison strComp = (ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
  540.             if (right == null || left == null || right.Length == 0 || left.Length == 0) {
  541.                 return false;
  542.             }
  543.             else if (right.Length == left.Length) {
  544.                 // if they are equal in length, just do a normal compare
  545.                 return String.Compare(right, left, strComp) == 0;
  546.             }
  547.             else {
  548.                 string shortString;
  549.                 string longString;
  550.                
  551.                 if (right.Length < left.Length) {
  552.                     shortString = right;
  553.                     longString = left;
  554.                 }
  555.                 else {
  556.                     shortString = left;
  557.                     longString = right;
  558.                 }
  559.                
  560.                 if (String.Compare(shortString, 0, longString, 0, shortString.Length, strComp) != 0) {
  561.                     return false;
  562.                 }
  563.                
  564.                 #if !PLATFORM_UNIX
  565.                 if (shortString.Length == 3 && shortString.EndsWith(":\\", StringComparison.Ordinal) && ((shortString[0] >= 'A' && shortString[0] <= 'Z') || (shortString[0] >= 'a' && shortString[0] <= 'z')))
  566.                     if (shortString.Length == 1 && shortString[0] == m_directorySeparator)
  567.                     #else
  568.                     #endif // !PLATFORM_UNIX
  569.                         return true;
  570.                
  571.                 return longString[shortString.Length] == m_directorySeparator;
  572.             }
  573.         }
  574.        
  575.        
  576.         //-------------------------------
  577.         // protected helper functions
  578.         //-------------------------------
  579.        
  580.         protected void AddSingleExpressionNoDuplicates(string expression)
  581.         {
  582.             int index = 0;
  583.            
  584.             m_expressionsArray = null;
  585.             m_expressions = null;
  586.            
  587.             if (this.m_list == null)
  588.                 this.m_list = new ArrayList();
  589.            
  590.             while (index < this.m_list.Count) {
  591.                 if (StringSubsetString((string)this.m_list[index], expression, m_ignoreCase)) {
  592.                     this.m_list.RemoveAt(index);
  593.                 }
  594.                 else if (StringSubsetString(expression, (string)this.m_list[index], m_ignoreCase)) {
  595.                     return;
  596.                 }
  597.                 else {
  598.                     index++;
  599.                 }
  600.             }
  601.             this.m_list.Add(expression);
  602.         }
  603.        
  604.         protected void Reduce()
  605.         {
  606.             CheckList();
  607.            
  608.             if (this.m_list == null)
  609.                 return;
  610.            
  611.             int j;
  612.            
  613.             for (int i = 0; i < this.m_list.Count - 1; i++) {
  614.                 j = i + 1;
  615.                
  616.                 while (j < this.m_list.Count) {
  617.                     if (StringSubsetString((string)this.m_list[j], (string)this.m_list[i], m_ignoreCase)) {
  618.                         this.m_list.RemoveAt(j);
  619.                     }
  620.                     else if (StringSubsetString((string)this.m_list[i], (string)this.m_list[j], m_ignoreCase)) {
  621.                         // write the value at j into position i, delete the value at position j and keep going.
  622.                         this.m_list[i] = this.m_list[j];
  623.                         this.m_list.RemoveAt(j);
  624.                         j = i + 1;
  625.                     }
  626.                     else {
  627.                         j++;
  628.                     }
  629.                 }
  630.             }
  631.         }
  632.        
  633.         [MethodImplAttribute(MethodImplOptions.InternalCall)]
  634.         static internal extern string GetLongPathName(string path);
  635.        
  636.         static internal string CanonicalizePath(string path)
  637.         {
  638.             return CanonicalizePath(path, true);
  639.         }
  640.        
  641.         [ResourceExposure(ResourceScope.None)]
  642.         // All internal, for string comparisons
  643.         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
  644.         static internal string CanonicalizePath(string path, bool needFullPath)
  645.         {
  646.            
  647.             #if !PLATFORM_UNIX
  648.             if (path.IndexOf('~') != -1)
  649.                 path = GetLongPathName(path);
  650.            
  651.             if (path.IndexOf(':', 2) != -1)
  652.                 throw new NotSupportedException(Environment.GetResourceString("Argument_PathFormatNotSupported"));
  653.             #endif // !PLATFORM_UNIX
  654.            
  655.             if (needFullPath) {
  656.                 string newPath = System.IO.Path.GetFullPathInternal(path);
  657.                 if (path.EndsWith(m_directorySeparator + ".")) {
  658.                     if (newPath.EndsWith(m_directorySeparator)) {
  659.                         newPath += ".";
  660.                     }
  661.                     else {
  662.                         newPath += m_directorySeparator + ".";
  663.                     }
  664.                 }
  665.                 return newPath;
  666.             }
  667.             else
  668.                 return path;
  669.         }
  670.     }
  671. }

Developer Fusion