SEBWIN-314: Started implementing filter rules with unit tests.
This commit is contained in:
		
							parent
							
								
									5209103c97
								
							
						
					
					
						commit
						6d1b282b33
					
				
					 7 changed files with 995 additions and 3 deletions
				
			
		
							
								
								
									
										553
									
								
								SafeExamBrowser.Browser.UnitTests/Filters/LegacyFilter.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										553
									
								
								SafeExamBrowser.Browser.UnitTests/Filters/LegacyFilter.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,553 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * This Source Code Form is subject to the terms of the Mozilla internal
 | 
				
			||||||
 | 
					 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
				
			||||||
 | 
					 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace SafeExamBrowser.Browser.UnitTests.Filters
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						internal class LegacyFilter
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							internal Regex scheme;
 | 
				
			||||||
 | 
							internal Regex user;
 | 
				
			||||||
 | 
							internal Regex password;
 | 
				
			||||||
 | 
							internal Regex host;
 | 
				
			||||||
 | 
							internal int? port;
 | 
				
			||||||
 | 
							internal Regex path;
 | 
				
			||||||
 | 
							internal Regex query;
 | 
				
			||||||
 | 
							internal Regex fragment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							internal LegacyFilter(string filterExpressionString)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								SEBURLFilterExpression URLFromString = new SEBURLFilterExpression(filterExpressionString);
 | 
				
			||||||
 | 
								try
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									this.scheme = RegexForFilterString(URLFromString.scheme);
 | 
				
			||||||
 | 
									this.user = RegexForFilterString(URLFromString.user);
 | 
				
			||||||
 | 
									this.password = RegexForFilterString(URLFromString.password);
 | 
				
			||||||
 | 
									this.host = RegexForHostFilterString(URLFromString.host);
 | 
				
			||||||
 | 
									this.port = URLFromString.port;
 | 
				
			||||||
 | 
									this.path = RegexForPathFilterString(URLFromString.path);
 | 
				
			||||||
 | 
									this.query = RegexForQueryFilterString(URLFromString.query);
 | 
				
			||||||
 | 
									this.fragment = RegexForFilterString(URLFromString.fragment);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								catch (Exception)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									throw;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Method comparing all components of a passed URL with the filter expression
 | 
				
			||||||
 | 
							// and returning YES (= allow or block) if it matches
 | 
				
			||||||
 | 
							internal bool IsMatch(Uri URLToFilter)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Regex filterComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// If a scheme is indicated in the filter expression, it has to match
 | 
				
			||||||
 | 
								filterComponent = scheme;
 | 
				
			||||||
 | 
								UriBuilder urlToFilterParts = new UriBuilder(URLToFilter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (filterComponent != null &&
 | 
				
			||||||
 | 
									!Regex.IsMatch(URLToFilter.Scheme, filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									// Scheme of the URL to filter doesn't match the one from the filter expression: Exit with matching = NO
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								string userInfo = URLToFilter.UserInfo;
 | 
				
			||||||
 | 
								filterComponent = user;
 | 
				
			||||||
 | 
								if (filterComponent != null &&
 | 
				
			||||||
 | 
									!Regex.IsMatch(urlToFilterParts.UserName, filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								filterComponent = password;
 | 
				
			||||||
 | 
								if (filterComponent != null &&
 | 
				
			||||||
 | 
									!Regex.IsMatch(urlToFilterParts.Password, filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								filterComponent = host;
 | 
				
			||||||
 | 
								if (filterComponent != null &&
 | 
				
			||||||
 | 
									!Regex.IsMatch(URLToFilter.Host, filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (port != null && URLToFilter.Port != port)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								filterComponent = path;
 | 
				
			||||||
 | 
								if (filterComponent != null &&
 | 
				
			||||||
 | 
									!Regex.IsMatch(URLToFilter.AbsolutePath.Trim(new char[] { '/' }), filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								string urlQuery = URLToFilter.GetComponents(UriComponents.Query, UriFormat.Unescaped);
 | 
				
			||||||
 | 
								filterComponent = query;
 | 
				
			||||||
 | 
								if (filterComponent != null)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									// If there's a query filter component, then we need to even filter empty URL query strings
 | 
				
			||||||
 | 
									// as the filter might either allow some specific queries or no query at all ("?." query filter)
 | 
				
			||||||
 | 
									if (urlQuery == null)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										urlQuery = "";
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (!Regex.IsMatch(urlQuery, filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										return false;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								string urlFragment = URLToFilter.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
 | 
				
			||||||
 | 
								filterComponent = fragment;
 | 
				
			||||||
 | 
								if (filterComponent != null &&
 | 
				
			||||||
 | 
									!Regex.IsMatch(urlFragment, filterComponent.ToString(), RegexOptions.IgnoreCase))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// URL matches the filter expression
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							internal static Regex RegexForFilterString(string filterString)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (string.IsNullOrEmpty(filterString))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									return null;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									string regexString = Regex.Escape(filterString);
 | 
				
			||||||
 | 
									regexString = regexString.Replace("\\*", ".*?");
 | 
				
			||||||
 | 
									// Add regex command characters for matching at start and end of a line (part)
 | 
				
			||||||
 | 
									regexString = string.Format("^{0}$", regexString);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									try
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
 | 
				
			||||||
 | 
										return regex;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									catch (Exception)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										throw;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							internal static Regex RegexForHostFilterString(string filterString)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (string.IsNullOrEmpty(filterString))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									return null;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									try
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										// Check if host string has a dot "." prefix to disable subdomain matching
 | 
				
			||||||
 | 
										if (filterString.Length > 1 && filterString.StartsWith("."))
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											// Get host string without the "." prefix
 | 
				
			||||||
 | 
											filterString = filterString.Substring(1);
 | 
				
			||||||
 | 
											// Get regex for host <*://example.com> (without possible subdomains)
 | 
				
			||||||
 | 
											return RegexForFilterString(filterString);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										// Allow subdomain matching: Create combined regex for <example.com> and <*.example.com>
 | 
				
			||||||
 | 
										string regexString = Regex.Escape(filterString);
 | 
				
			||||||
 | 
										regexString = regexString.Replace("\\*", ".*?");
 | 
				
			||||||
 | 
										// Add regex command characters for matching at start and end of a line (part)
 | 
				
			||||||
 | 
										regexString = string.Format("^(({0})|(.*?\\.{0}))$", regexString);
 | 
				
			||||||
 | 
										Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
 | 
				
			||||||
 | 
										return regex;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									catch (Exception)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										throw;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							internal static Regex RegexForPathFilterString(string filterString)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								// Trim a possible trailing slash "/", we will instead add a rule to also match paths to directories without trailing slash
 | 
				
			||||||
 | 
								filterString = filterString.TrimEnd(new char[] { '/' });
 | 
				
			||||||
 | 
								;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (string.IsNullOrEmpty(filterString))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									return null;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									try
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										// Check if path string ends with a "/*" for matching contents of a directory
 | 
				
			||||||
 | 
										if (filterString.EndsWith("/*"))
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											// As the path filter string matches for a directory, we need to add a string to match directories without trailing slash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// Get path string without the "/*" suffix
 | 
				
			||||||
 | 
											string filterStringDirectory = filterString.Substring(0, filterString.Length - 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											string regexString = Regex.Escape(filterString);
 | 
				
			||||||
 | 
											regexString = regexString.Replace("\\*", ".*?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											string regexStringDir = Regex.Escape(filterString);
 | 
				
			||||||
 | 
											regexStringDir = regexStringDir.Replace("\\*", ".*?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// Add regex command characters for matching at start and end of a line (part)
 | 
				
			||||||
 | 
											regexString = string.Format("^(({0})|({1}))$", regexString, regexStringDir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
 | 
				
			||||||
 | 
											return regex;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										else
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											return RegexForFilterString(filterString);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									catch (Exception)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										throw;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							internal static Regex RegexForQueryFilterString(string filterString)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (string.IsNullOrEmpty(filterString))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									return null;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									if (filterString.Equals("."))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										// Add regex command characters for matching at start and end of a line (part)
 | 
				
			||||||
 | 
										// and regex for no string allowed
 | 
				
			||||||
 | 
										string regexString = @"^$";
 | 
				
			||||||
 | 
										try
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
 | 
				
			||||||
 | 
											return regex;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										catch (Exception)
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											throw;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									else
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										return RegexForFilterString(filterString);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public override string ToString()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								StringBuilder expressionString = new StringBuilder();
 | 
				
			||||||
 | 
								string part;
 | 
				
			||||||
 | 
								expressionString.Append("^");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/// Scheme
 | 
				
			||||||
 | 
								if (this.scheme != null)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									// If there is a regex filter for scheme
 | 
				
			||||||
 | 
									// get stripped regex pattern
 | 
				
			||||||
 | 
									part = StringForRegexFilter(this.scheme);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									// otherwise use the regex wildcard pattern for scheme
 | 
				
			||||||
 | 
									part = @".*?";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expressionString.AppendFormat("{0}:\\/\\/", part);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/// User/Password
 | 
				
			||||||
 | 
								if (this.user != null)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									part = StringForRegexFilter(this.user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									expressionString.Append(part);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (this.password != null)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										expressionString.AppendFormat(":{0}@", StringForRegexFilter(this.password));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									else
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										expressionString.Append("@");
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/// Host
 | 
				
			||||||
 | 
								string hostPort = "";
 | 
				
			||||||
 | 
								if (this.host != null)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									hostPort = StringForRegexFilter(this.host);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									hostPort = ".*?";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/// Port
 | 
				
			||||||
 | 
								if (this.port != null && this.port > 0 && this.port <= 65535)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									hostPort = string.Format("{0}:{1}", hostPort, this.port);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// When there is a host, but no path
 | 
				
			||||||
 | 
								if (this.host != null && this.path == null)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									hostPort = string.Format("(({0})|({0}\\/.*?))", hostPort);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expressionString.Append(hostPort);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/// Path
 | 
				
			||||||
 | 
								if (this.path != null)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									string path = StringForRegexFilter(this.path);
 | 
				
			||||||
 | 
									if (path.StartsWith("\\/"))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										expressionString.Append(path);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									else
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										expressionString.AppendFormat("\\/{0}", path);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/// Query
 | 
				
			||||||
 | 
								if (this.query != null)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									// Check for special case Query = "?." which means no query string is allowed
 | 
				
			||||||
 | 
									if (StringForRegexFilter(this.query).Equals("."))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										expressionString.AppendFormat("[^\\?]");
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									else
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										expressionString.AppendFormat("\\?{0}", StringForRegexFilter(this.query));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									expressionString.AppendFormat("(()|(\\?.*?))");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/// Fragment
 | 
				
			||||||
 | 
								if (this.fragment != null)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									expressionString.AppendFormat("#{0}", StringForRegexFilter(this.fragment));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expressionString.Append("$");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return expressionString.ToString();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							internal string StringForRegexFilter(Regex regexFilter)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								// Get pattern string from regular expression
 | 
				
			||||||
 | 
								string regexPattern = regexFilter.ToString();
 | 
				
			||||||
 | 
								if (regexPattern.Length <= 2)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									return "";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// Remove the regex command characters for matching at start and end of a line
 | 
				
			||||||
 | 
								regexPattern = regexPattern.Substring(1, regexPattern.Length - 2);
 | 
				
			||||||
 | 
								return regexPattern;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private class SEBURLFilterExpression
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								internal string scheme;
 | 
				
			||||||
 | 
								internal string user;
 | 
				
			||||||
 | 
								internal string password;
 | 
				
			||||||
 | 
								internal string host;
 | 
				
			||||||
 | 
								internal int? port;
 | 
				
			||||||
 | 
								internal string path;
 | 
				
			||||||
 | 
								internal string query;
 | 
				
			||||||
 | 
								internal string fragment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								internal SEBURLFilterExpression(string filterExpressionString)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									if (!string.IsNullOrEmpty(filterExpressionString))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										/// Convert Uri to a SEBURLFilterExpression
 | 
				
			||||||
 | 
										string splitURLRegexPattern = @"(?:([^\:]*)\:\/\/)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^\/\:]*))?(?:\:([0-9\*]*))?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
 | 
				
			||||||
 | 
										Regex splitURLRegex = new Regex(splitURLRegexPattern);
 | 
				
			||||||
 | 
										Match regexMatch = splitURLRegex.Match(filterExpressionString);
 | 
				
			||||||
 | 
										if (regexMatch.Success == false)
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											return;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										this.scheme = regexMatch.Groups[1].Value;
 | 
				
			||||||
 | 
										this.user = regexMatch.Groups[2].Value;
 | 
				
			||||||
 | 
										this.password = regexMatch.Groups[3].Value;
 | 
				
			||||||
 | 
										this.host = regexMatch.Groups[4].Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Treat a special case when a query is interpreted as part of the host address
 | 
				
			||||||
 | 
										if (this.host.Contains("?"))
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											string splitURLRegexPattern2 = @"([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
 | 
				
			||||||
 | 
											Regex splitURLRegex2 = new Regex(splitURLRegexPattern2);
 | 
				
			||||||
 | 
											Match regexMatch2 = splitURLRegex2.Match(this.host);
 | 
				
			||||||
 | 
											if (regexMatch.Success == false)
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												return;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											this.host = regexMatch2.Groups[1].Value;
 | 
				
			||||||
 | 
											this.port = null;
 | 
				
			||||||
 | 
											this.path = "";
 | 
				
			||||||
 | 
											this.query = regexMatch2.Groups[2].Value;
 | 
				
			||||||
 | 
											this.fragment = regexMatch2.Groups[3].Value;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										else
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											string portNumber = regexMatch.Groups[5].Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// We only want a port if the filter expression string explicitely defines one!
 | 
				
			||||||
 | 
											if (portNumber.Length == 0 || portNumber == "*")
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												this.port = null;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											else
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												this.port = UInt16.Parse(portNumber);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											this.path = regexMatch.Groups[6].Value.Trim(new char[] { '/' });
 | 
				
			||||||
 | 
											this.query = regexMatch.Groups[7].Value;
 | 
				
			||||||
 | 
											this.fragment = regexMatch.Groups[8].Value;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								internal static string User(string userInfo)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									string user = "";
 | 
				
			||||||
 | 
									if (!string.IsNullOrEmpty(userInfo))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										int userPasswordSeparator = userInfo.IndexOf(":");
 | 
				
			||||||
 | 
										if (userPasswordSeparator == -1)
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											user = userInfo;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										else
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											if (userPasswordSeparator != 0)
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												user = userInfo.Substring(0, userPasswordSeparator);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return user;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								internal static string Password(string userInfo)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									string password = "";
 | 
				
			||||||
 | 
									if (!string.IsNullOrEmpty(userInfo))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										int userPasswordSeparator = userInfo.IndexOf(":");
 | 
				
			||||||
 | 
										if (userPasswordSeparator != -1)
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											if (userPasswordSeparator < userInfo.Length - 1)
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												password = userInfo.Substring(userPasswordSeparator + 1, userInfo.Length - 1 - userPasswordSeparator);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return password;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								internal SEBURLFilterExpression(string scheme, string user, string password, string host, int port, string path, string query, string fragment)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									this.scheme = scheme;
 | 
				
			||||||
 | 
									this.user = user;
 | 
				
			||||||
 | 
									this.password = password;
 | 
				
			||||||
 | 
									this.host = host;
 | 
				
			||||||
 | 
									this.port = port;
 | 
				
			||||||
 | 
									this.path = path;
 | 
				
			||||||
 | 
									this.query = query;
 | 
				
			||||||
 | 
									this.fragment = fragment;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								public override string ToString()
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									StringBuilder expressionString = new StringBuilder();
 | 
				
			||||||
 | 
									if (!string.IsNullOrEmpty(this.scheme))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										if (!string.IsNullOrEmpty(this.host))
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											expressionString.AppendFormat("{0}://", this.scheme);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										else
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											expressionString.AppendFormat("{0}:", this.scheme);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (!string.IsNullOrEmpty(this.user))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										expressionString.Append(this.user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (!string.IsNullOrEmpty(this.password))
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											expressionString.AppendFormat(":{0}@", this.password);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										else
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											expressionString.Append("@");
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (!string.IsNullOrEmpty(this.host))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										expressionString.Append(this.host);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (this.port != null && this.port > 0 && this.port <= 65535)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										expressionString.AppendFormat(":{0}", this.port);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (!string.IsNullOrEmpty(this.path))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										if (this.path.StartsWith("/"))
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											expressionString.Append(this.path);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										else
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											expressionString.AppendFormat("/{0}", this.path);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (!string.IsNullOrEmpty(this.query))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										expressionString.AppendFormat("?{0}", this.query);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (!string.IsNullOrEmpty(this.fragment))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										expressionString.AppendFormat("#{0}", this.fragment);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return expressionString.ToString();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,12 @@
 | 
				
			||||||
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
					 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
 | 
					using Microsoft.VisualStudio.TestTools.UnitTesting;
 | 
				
			||||||
 | 
					using SafeExamBrowser.Browser.Contracts.Filters;
 | 
				
			||||||
using SafeExamBrowser.Browser.Filters.Rules;
 | 
					using SafeExamBrowser.Browser.Filters.Rules;
 | 
				
			||||||
 | 
					using SafeExamBrowser.Settings.Browser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
 | 
					namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -21,5 +25,43 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			sut = new RegexRule();
 | 
								sut = new RegexRule();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							public void MustIgnoreCase()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings { Expression = Regex.Escape("http://www.test.org/path/file.txt?param=123") });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
 | 
				
			||||||
 | 
								Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings { Expression = Regex.Escape("HTTP://WWW.TEST.ORG/PATH/FILE.TXT?PARAM=123") });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
 | 
				
			||||||
 | 
								Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							public void MustInitializeResult()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								foreach (var result in Enum.GetValues(typeof(FilterResult)))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									sut.Initialize(new FilterRuleSettings { Expression = "", Result = (FilterResult) result });
 | 
				
			||||||
 | 
									Assert.AreEqual(result, sut.Result);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							[ExpectedException(typeof(ArgumentNullException))]
 | 
				
			||||||
 | 
							public void MustNotAllowUndefinedExpression()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							[ExpectedException(typeof(ArgumentException))]
 | 
				
			||||||
 | 
							public void MustValidateExpression()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings { Expression = "ç+\"}%&*/(+)=?{=*+¦]@#°§]`?´^¨'°[¬|¢" });
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,11 @@
 | 
				
			||||||
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
					 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
 | 
					using Microsoft.VisualStudio.TestTools.UnitTesting;
 | 
				
			||||||
 | 
					using SafeExamBrowser.Browser.Contracts.Filters;
 | 
				
			||||||
using SafeExamBrowser.Browser.Filters.Rules;
 | 
					using SafeExamBrowser.Browser.Filters.Rules;
 | 
				
			||||||
 | 
					using SafeExamBrowser.Settings.Browser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
 | 
					namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -21,5 +24,269 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			sut = new SimplifiedRule();
 | 
								sut = new SimplifiedRule();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							public void TestAlphanumericExpressionAsHost()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var expression = "hostname-123";
 | 
				
			||||||
 | 
								var positive = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									$"scheme://{expression}.org",
 | 
				
			||||||
 | 
									$"scheme://www.{expression}.org",
 | 
				
			||||||
 | 
									$"scheme://subdomain.{expression}.com",
 | 
				
			||||||
 | 
									$"scheme://subdomain-1.subdomain-2.{expression}.org",
 | 
				
			||||||
 | 
									$"scheme://user:password@www.{expression}.org/path/file.txt?param=123#fragment"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								var negative = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									$"scheme://hostname.org",
 | 
				
			||||||
 | 
									$"scheme://hostname-12.org",
 | 
				
			||||||
 | 
									$"scheme://{expression}4.org",
 | 
				
			||||||
 | 
									$"scheme://{expression}.realhost.org",
 | 
				
			||||||
 | 
									$"scheme://subdomain.{expression}.realhost.org",
 | 
				
			||||||
 | 
									$"scheme://www.realhost.{expression}",
 | 
				
			||||||
 | 
									$"{expression}://www.host.org",
 | 
				
			||||||
 | 
									$"scheme://www.host.org/{expression}/path",
 | 
				
			||||||
 | 
									$"scheme://www.host.org/path?param={expression}",
 | 
				
			||||||
 | 
									$"scheme://{expression}:password@www.host.org",
 | 
				
			||||||
 | 
									$"scheme://user:{expression}@www.host.org",
 | 
				
			||||||
 | 
									$"scheme://user:password@www.host.org/path?param=123#{expression}"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Execute(expression, positive, negative, false);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							public void TestHostExpressionWithDomain()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var expression = "123-hostname.org";
 | 
				
			||||||
 | 
								var positive = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									$"scheme://{expression}",
 | 
				
			||||||
 | 
									$"scheme://www.{expression}",
 | 
				
			||||||
 | 
									$"scheme://subdomain.{expression}",
 | 
				
			||||||
 | 
									$"scheme://subdomain-1.subdomain-2.{expression}",
 | 
				
			||||||
 | 
									$"scheme://user:password@www.{expression}/path/file.txt?param=123#fragment"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								var negative = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									$"scheme://123.org",
 | 
				
			||||||
 | 
									$"scheme://123-host.org",
 | 
				
			||||||
 | 
									$"scheme://{expression}.com",
 | 
				
			||||||
 | 
									$"scheme://{expression}s.org",
 | 
				
			||||||
 | 
									$"scheme://{expression}.realhost.org",
 | 
				
			||||||
 | 
									$"scheme://subdomain.{expression}.realhost.org",
 | 
				
			||||||
 | 
									$"scheme{expression}://www.host.org",
 | 
				
			||||||
 | 
									$"scheme://www.host.org/{expression}/path",
 | 
				
			||||||
 | 
									$"scheme://www.host.org/path?param={expression}",
 | 
				
			||||||
 | 
									$"scheme://{expression}:password@www.host.org",
 | 
				
			||||||
 | 
									$"scheme://user:{expression}@www.host.org",
 | 
				
			||||||
 | 
									$"scheme://user:password@www.host.org/path?param=123#{expression}"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Execute(expression, positive, negative);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							public void TestHostExpressionWithWildcard()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var expression = "test.*.org";
 | 
				
			||||||
 | 
								var positive = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"scheme://test.host.org",
 | 
				
			||||||
 | 
									"scheme://test.host.domain.org",
 | 
				
			||||||
 | 
									"scheme://subdomain.test.host.org",
 | 
				
			||||||
 | 
									"scheme://user:password@test.domain.org/path/file.txt?param=123#fragment"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								var negative = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"scheme://test.org",
 | 
				
			||||||
 | 
									"scheme://host.com/test.host.org",
 | 
				
			||||||
 | 
									"scheme://www.host.org/test.host.org/path",
 | 
				
			||||||
 | 
									"scheme://www.host.org/path?param=test.host.org",
 | 
				
			||||||
 | 
									"scheme://test.host.org:password@www.host.org",
 | 
				
			||||||
 | 
									"scheme://user:test.host.org@www.host.org",
 | 
				
			||||||
 | 
									"scheme://user:password@www.host.org/path?param=123#test.host.org"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Execute(expression, positive, negative);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							public void TestHostExpressionWithWildcardAsSuffix()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var expression = "test.host.*";
 | 
				
			||||||
 | 
								var positive = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"scheme://test.host.org",
 | 
				
			||||||
 | 
									"scheme://test.host.domain.org",
 | 
				
			||||||
 | 
									"scheme://subdomain.test.host.org",
 | 
				
			||||||
 | 
									"scheme://user:password@test.host.org/path/file.txt?param=123#fragment"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								var negative = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"scheme://host.com",
 | 
				
			||||||
 | 
									"scheme://test.host",
 | 
				
			||||||
 | 
									"scheme://host.com/test.host.txt"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Execute(expression, positive, negative);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							public void TestHostExpressionWithWildcardAsPrefix()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var expression = "*.org";
 | 
				
			||||||
 | 
								var positive = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"scheme://domain.org",
 | 
				
			||||||
 | 
									"scheme://test.host.org",
 | 
				
			||||||
 | 
									"scheme://test.host.domain.org",
 | 
				
			||||||
 | 
									"scheme://user:password@www.host.org/path/file.txt?param=123#fragment"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								var negative = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"scheme://org",
 | 
				
			||||||
 | 
									"scheme://host.com",
 | 
				
			||||||
 | 
									"scheme://test.net",
 | 
				
			||||||
 | 
									"scheme://test.ch",
 | 
				
			||||||
 | 
									"scheme://host.com/test.org"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Execute(expression, positive, negative);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							public void TestHostExpressionWithExactSubdomain()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var expression = ".www.host.org";
 | 
				
			||||||
 | 
								var positive = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"scheme://www.host.org",
 | 
				
			||||||
 | 
									"scheme://user:password@www.host.org/path/file.txt?param=123#fragment"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								var negative = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"scheme://host.org",
 | 
				
			||||||
 | 
									"scheme://test.www.host.org",
 | 
				
			||||||
 | 
									"scheme://www.host.org.com"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Execute(expression, positive, negative);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							public void TestExpressionWithPortNumber()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var expression = "host.org:2020";
 | 
				
			||||||
 | 
								var positive = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"scheme://host.org:2020",
 | 
				
			||||||
 | 
									"scheme://www.host.org:2020",
 | 
				
			||||||
 | 
									"scheme://user:password@www.host.org:2020/path/file.txt?param=123#fragment"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								var negative = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"scheme://host.org",
 | 
				
			||||||
 | 
									"scheme://www.host.org",
 | 
				
			||||||
 | 
									"scheme://www.host.org:2",
 | 
				
			||||||
 | 
									"scheme://www.host.org:20",
 | 
				
			||||||
 | 
									"scheme://www.host.org:202",
 | 
				
			||||||
 | 
									"scheme://www.host.org:20202"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Execute(expression, positive, negative);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							public void MustIgnoreCase()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings { Expression = "http://www.test.org/path/file.txt?param=123" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
 | 
				
			||||||
 | 
								Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings { Expression = "HTTP://WWW.TEST.ORG/PATH/FILE.TXT?PARAM=123" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
 | 
				
			||||||
 | 
								Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO
 | 
				
			||||||
 | 
							//[TestMethod]
 | 
				
			||||||
 | 
							//public void MustIgnoreTrailingSlash()
 | 
				
			||||||
 | 
							//{
 | 
				
			||||||
 | 
							//	Assert.Fail();
 | 
				
			||||||
 | 
							//}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							//[TestMethod]
 | 
				
			||||||
 | 
							//public void MustAllowWildcard()
 | 
				
			||||||
 | 
							//{
 | 
				
			||||||
 | 
							//	Assert.Fail();
 | 
				
			||||||
 | 
							//}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							public void MustInitializeResult()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								foreach (var result in Enum.GetValues(typeof(FilterResult)))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									sut.Initialize(new FilterRuleSettings { Expression = "*", Result = (FilterResult) result });
 | 
				
			||||||
 | 
									Assert.AreEqual(result, sut.Result);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							[ExpectedException(typeof(ArgumentNullException))]
 | 
				
			||||||
 | 
							public void MustNotAllowUndefinedExpression()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[TestMethod]
 | 
				
			||||||
 | 
							public void MustValidateExpression()
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var invalid = new[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									".", "+", "\"", "ç", "%", "&", "/", "(", ")", "=", "?", "^", "!", "[", "]", "{", "}", "¦", "@", "#", "°", "§", "¬", "|", "¢", "´", "'", "`", "~", "<", ">", "\\"
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings { Expression = "*" });
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings { Expression = "a" });
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings { Expression = "A" });
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings { Expression = "0" });
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings { Expression = "abcdeFGHIJK-12345" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								foreach (var expression in invalid)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Assert.ThrowsException<ArgumentException>(() => sut.Initialize(new FilterRuleSettings { Expression = expression }));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private void Execute(string expression, string[] positive, string[] negative, bool testLegacy = true)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var legacy = new LegacyFilter(expression);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								sut.Initialize(new FilterRuleSettings { Expression = expression });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								foreach (var url in positive)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Assert.IsTrue(sut.IsMatch(new Request { Url = url }), url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (testLegacy)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Assert.IsTrue(legacy.IsMatch(new Uri(url)), url);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								foreach (var url in negative)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Assert.IsFalse(sut.IsMatch(new Request { Url = url }), url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (testLegacy)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Assert.IsFalse(legacy.IsMatch(new Uri(url)), url);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,6 +80,7 @@
 | 
				
			||||||
    </Reference>
 | 
					    </Reference>
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <Compile Include="Filters\LegacyFilter.cs" />
 | 
				
			||||||
    <Compile Include="Filters\RequestFilterTests.cs" />
 | 
					    <Compile Include="Filters\RequestFilterTests.cs" />
 | 
				
			||||||
    <Compile Include="Filters\RuleFactoryTests.cs" />
 | 
					    <Compile Include="Filters\RuleFactoryTests.cs" />
 | 
				
			||||||
    <Compile Include="Filters\Rules\RegexRuleTests.cs" />
 | 
					    <Compile Include="Filters\Rules\RegexRuleTests.cs" />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@
 | 
				
			||||||
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
					 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
using System.Text.RegularExpressions;
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
using SafeExamBrowser.Browser.Contracts.Filters;
 | 
					using SafeExamBrowser.Browser.Contracts.Filters;
 | 
				
			||||||
using SafeExamBrowser.Settings.Browser;
 | 
					using SafeExamBrowser.Settings.Browser;
 | 
				
			||||||
| 
						 | 
					@ -20,6 +21,8 @@ namespace SafeExamBrowser.Browser.Filters.Rules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public void Initialize(FilterRuleSettings settings)
 | 
							public void Initialize(FilterRuleSettings settings)
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
 | 
								ValidateExpression(settings.Expression);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			expression = settings.Expression;
 | 
								expression = settings.Expression;
 | 
				
			||||||
			Result = settings.Result;
 | 
								Result = settings.Result;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -28,5 +31,22 @@ namespace SafeExamBrowser.Browser.Filters.Rules
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			return Regex.IsMatch(request.Url, expression, RegexOptions.IgnoreCase);
 | 
								return Regex.IsMatch(request.Url, expression, RegexOptions.IgnoreCase);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private void ValidateExpression(string expression)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (expression == default(string))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									throw new ArgumentNullException(nameof(expression));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								try
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Regex.Match("", expression);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								catch (Exception e)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									throw new ArgumentException($"Invalid regular expression!", nameof(expression), e);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@
 | 
				
			||||||
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
					 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
using System.Text.RegularExpressions;
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
using SafeExamBrowser.Browser.Contracts.Filters;
 | 
					using SafeExamBrowser.Browser.Contracts.Filters;
 | 
				
			||||||
using SafeExamBrowser.Settings.Browser;
 | 
					using SafeExamBrowser.Settings.Browser;
 | 
				
			||||||
| 
						 | 
					@ -14,19 +15,123 @@ namespace SafeExamBrowser.Browser.Filters.Rules
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	internal class SimplifiedRule : IRule
 | 
						internal class SimplifiedRule : IRule
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		private string expression;
 | 
							private const string URL_DELIMITER_PATTERN = @"(?:([^\:]*)\:\/\/)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^\/\:]*))?(?:\:([0-9\*]*))?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private Regex fragment;
 | 
				
			||||||
 | 
							private Regex host;
 | 
				
			||||||
 | 
							private Regex password;
 | 
				
			||||||
 | 
							private Regex path;
 | 
				
			||||||
 | 
							private int? port;
 | 
				
			||||||
 | 
							private Regex query;
 | 
				
			||||||
 | 
							private Regex scheme;
 | 
				
			||||||
 | 
							private Regex user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public FilterResult Result { get; private set; }
 | 
							public FilterResult Result { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public void Initialize(FilterRuleSettings settings)
 | 
							public void Initialize(FilterRuleSettings settings)
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			expression = settings.Expression.Replace("*", @".*");
 | 
								ValidateExpression(settings.Expression);
 | 
				
			||||||
 | 
								ParseExpression(settings.Expression);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			Result = settings.Result;
 | 
								Result = settings.Result;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public bool IsMatch(Request request)
 | 
							public bool IsMatch(Request request)
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			return Regex.IsMatch(request.Url, expression, RegexOptions.IgnoreCase);
 | 
								var url = new Uri(request.Url, UriKind.Absolute);
 | 
				
			||||||
 | 
								var isMatch = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								//isMatch &= scheme == default(Regex) || ...;
 | 
				
			||||||
 | 
								//isMatch &= user == default(Regex) || ...;
 | 
				
			||||||
 | 
								//isMatch &= password == default(Regex) || ...;
 | 
				
			||||||
 | 
								isMatch &= host.IsMatch(url.Host);
 | 
				
			||||||
 | 
								isMatch &= !port.HasValue || port == url.Port;
 | 
				
			||||||
 | 
								//isMatch &= path == default(Regex) || ...;
 | 
				
			||||||
 | 
								//isMatch &= query == default(Regex) || ...;
 | 
				
			||||||
 | 
								//isMatch &= fragment == default(Regex) || ...;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return isMatch;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private void ParseExpression(string expression)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var match = Regex.Match(expression, URL_DELIMITER_PATTERN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								//ParseScheme(match.Groups[1].Value);
 | 
				
			||||||
 | 
								//ParseUser(match.Groups[2].Value);
 | 
				
			||||||
 | 
								//ParsePassword(match.Groups[3].Value);
 | 
				
			||||||
 | 
								ParseHost(match.Groups[4].Value);
 | 
				
			||||||
 | 
								ParsePort(match.Groups[5].Value);
 | 
				
			||||||
 | 
								//ParsePath(match.Groups[6].Value);
 | 
				
			||||||
 | 
								//ParseQuery(match.Groups[7].Value);
 | 
				
			||||||
 | 
								//ParseFragment(match.Groups[8].Value);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private void ParseHost(string expression)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var hasToplevelDomain = Regex.IsMatch(expression, @"\.+");
 | 
				
			||||||
 | 
								var hasSubdomain = Regex.IsMatch(expression, @"\.{2,}");
 | 
				
			||||||
 | 
								var allowOnlyExactSubdomain = expression.StartsWith(".");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (allowOnlyExactSubdomain)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									expression = expression.Substring(1);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								expression = Regex.Escape(expression);
 | 
				
			||||||
 | 
								expression = ReplaceWildcard(expression);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!hasToplevelDomain)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									expression = $@"{expression}(\.[a-z]+)";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!hasSubdomain && !allowOnlyExactSubdomain)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									expression = $@"(.+?\.)*{expression}";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								host = Build(expression);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private void ParsePort(string expression)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (int.TryParse(expression, out var port))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									this.port = port;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private Regex Build(string expression)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								return new Regex($"^{expression}$", RegexOptions.IgnoreCase);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private string ReplaceWildcard(string expression)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								return expression.Replace(@"\*", ".*?");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private void ValidateExpression(string expression)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (expression == default(string))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									throw new ArgumentNullException(nameof(expression));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!Regex.IsMatch(expression, @"[a-zA-Z0-9\*]+"))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									throw new ArgumentException("Expression must consist of at least one alphanumeric character or asterisk!", nameof(expression));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								try
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Regex.Match(expression, URL_DELIMITER_PATTERN);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								catch (Exception e)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									throw new ArgumentException("Expression is not a valid simplified filter expression!", nameof(expression), e);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,6 +29,10 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
 | 
				
			||||||
			MapApplicationLogAccess(rawData, settings);
 | 
								MapApplicationLogAccess(rawData, settings);
 | 
				
			||||||
			MapKioskMode(rawData, settings);
 | 
								MapKioskMode(rawData, settings);
 | 
				
			||||||
			MapUserAgentMode(rawData, settings);
 | 
								MapUserAgentMode(rawData, settings);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// TODO: Automatically create filter rule for start URL!
 | 
				
			||||||
 | 
								//			-> Only if filter active
 | 
				
			||||||
 | 
								//			-> Create mechanism for post-processing of settings?
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		private void MapAudioSettings(string key, object value, ApplicationSettings settings)
 | 
							private void MapAudioSettings(string key, object value, ApplicationSettings settings)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue