1+ // Licensed to the .NET Foundation under one or more agreements.
2+ // The .NET Foundation licenses this file to you under the MIT license.
3+ // See the LICENSE file in the project root for more information.
4+
5+ using System . Text . RegularExpressions ;
6+
7+ namespace DevProxy . Abstractions ;
8+
9+ enum UrlRegexComparisonResult
10+ {
11+ /// <summary>
12+ /// The first pattern is broader than the second pattern.
13+ /// </summary>
14+ FirstPatternBroader ,
15+
16+ /// <summary>
17+ /// The second pattern is broader than the first pattern.
18+ /// </summary>
19+ SecondPatternBroader ,
20+
21+ /// <summary>
22+ /// The patterns are equivalent.
23+ /// </summary>
24+ PatternsEquivalent ,
25+
26+ /// <summary>
27+ /// The patterns are mutually exclusive.
28+ /// </summary>
29+ PatternsMutuallyExclusive
30+ }
31+
32+ class UrlRegexComparer
33+ {
34+ /// <summary>
35+ /// Compares two URL patterns and returns a value indicating their
36+ // relationship.
37+ /// </summary>
38+ /// <param name="pattern1">First URL pattern</param>
39+ /// <param name="pattern2">Second URL pattern</param>
40+ /// <returns>1 when the first pattern is broader; -1 when the second pattern
41+ /// is broader or patterns are mutually exclusive; 0 when the patterns are
42+ /// equal</returns>
43+ public static UrlRegexComparisonResult CompareRegexPatterns ( string pattern1 , string pattern2 )
44+ {
45+ var regex1 = new Regex ( ProxyUtils . PatternToRegex ( pattern1 ) ) ;
46+ var regex2 = new Regex ( ProxyUtils . PatternToRegex ( pattern2 ) ) ;
47+
48+ // Generate test URLs based on patterns
49+ var testUrls = GenerateTestUrls ( pattern1 , pattern2 ) ;
50+
51+ var matches1 = testUrls . Where ( url => regex1 . IsMatch ( url ) ) . ToList ( ) ;
52+ var matches2 = testUrls . Where ( url => regex2 . IsMatch ( url ) ) . ToList ( ) ;
53+
54+ bool pattern1MatchesAll = matches2 . All ( regex1 . IsMatch ) ;
55+ bool pattern2MatchesAll = matches1 . All ( regex2 . IsMatch ) ;
56+
57+ if ( pattern1MatchesAll && ! pattern2MatchesAll )
58+ // Pattern 1 is broader
59+ return UrlRegexComparisonResult . FirstPatternBroader ;
60+ else if ( pattern2MatchesAll && ! pattern1MatchesAll )
61+ // Pattern 2 is broader
62+ return UrlRegexComparisonResult . SecondPatternBroader ;
63+ else if ( pattern1MatchesAll && pattern2MatchesAll )
64+ // Patterns are equivalent
65+ return UrlRegexComparisonResult . PatternsEquivalent ;
66+ else
67+ // Patterns have different matching sets
68+ return UrlRegexComparisonResult . PatternsMutuallyExclusive ;
69+ }
70+
71+ private static List < string > GenerateTestUrls ( string pattern1 , string pattern2 )
72+ {
73+ var urls = new HashSet < string > ( ) ;
74+
75+ // Extract domains and paths from patterns
76+ var domains = ExtractDomains ( pattern1 )
77+ . Concat ( ExtractDomains ( pattern2 ) )
78+ . Distinct ( )
79+ . ToList ( ) ;
80+
81+ var paths = ExtractPaths ( pattern1 )
82+ . Concat ( ExtractPaths ( pattern2 ) )
83+ . Distinct ( )
84+ . ToList ( ) ;
85+
86+ // Generate combinations
87+ foreach ( var domain in domains )
88+ {
89+ foreach ( var path in paths )
90+ {
91+ urls . Add ( $ "https://{ domain } /{ path } ") ;
92+ }
93+
94+ // Add variants
95+ urls . Add ( $ "https://{ domain } /") ;
96+ urls . Add ( $ "https://sub.{ domain } /path") ;
97+ urls . Add ( $ "https://other-{ domain } /different") ;
98+ }
99+
100+ return urls . ToList ( ) ;
101+ }
102+
103+ private static HashSet < string > ExtractDomains ( string pattern )
104+ {
105+ var domains = new HashSet < string > ( ) ;
106+
107+ // Extract literal domains
108+ var domainMatch = Regex . Match ( Regex . Unescape ( pattern ) , @"https://([^/\s]+)" ) ;
109+ if ( domainMatch . Success )
110+ {
111+ var domain = domainMatch . Groups [ 1 ] . Value ;
112+ if ( ! domain . Contains ( ".*" ) )
113+ domains . Add ( domain ) ;
114+ }
115+
116+ // Add test domains
117+ domains . Add ( "example.com" ) ;
118+ domains . Add ( "test.com" ) ;
119+
120+ return domains ;
121+ }
122+
123+ private static HashSet < string > ExtractPaths ( string pattern )
124+ {
125+ var paths = new HashSet < string > ( ) ;
126+
127+ // Extract literal paths
128+ var pathMatch = Regex . Match ( pattern , @"https://[^/]+(/[^/\s]+)" ) ;
129+ if ( pathMatch . Success )
130+ {
131+ var path = pathMatch . Groups [ 1 ] . Value ;
132+ if ( ! path . Contains ( ".*" ) )
133+ paths . Add ( path . TrimStart ( '/' ) ) ;
134+ }
135+
136+ // Add test paths
137+ paths . Add ( "api" ) ;
138+ paths . Add ( "users" ) ;
139+ paths . Add ( "path1/path2" ) ;
140+
141+ return paths ;
142+ }
143+ }
0 commit comments