@@ -29,8 +29,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2929using System . Collections . Generic ;
3030using System . ComponentModel ;
3131using System . Diagnostics . CodeAnalysis ;
32+ using System . Globalization ;
3233using System . IO ;
3334using System . Linq ;
35+ using System . Net ;
3436using System . Text ;
3537using System . Text . RegularExpressions ;
3638using System . Xml ;
@@ -43,7 +45,7 @@ namespace DigitalRuby.IPBanCore
4345 /// <summary>
4446 /// Configuration for ip ban app
4547 /// </summary>
46- public class IPBanConfig : IIsWhitelisted
48+ public sealed class IPBanConfig : IIsWhitelisted
4749 {
4850 /// <summary>
4951 /// Allow temporary change of config
@@ -124,6 +126,7 @@ public void Dispose()
124126 private readonly bool clearBannedIPAddressesOnRestart ;
125127 private readonly bool clearFailedLoginsOnSuccessfulLogin ;
126128 private readonly bool processInternalIPAddresses ;
129+ private readonly string truncateUserNameChars ;
127130 private readonly HashSet < string > userNameWhitelist = new ( StringComparer . Ordinal ) ;
128131 private readonly int userNameWhitelistMaximumEditDistance = 2 ;
129132 private readonly Regex userNameWhitelistRegex ;
@@ -190,6 +193,8 @@ private IPBanConfig(XmlDocument doc, IDnsLookup dns = null, IDnsServerList dnsLi
190193 TryGetConfig < bool > ( "ClearBannedIPAddressesOnRestart" , ref clearBannedIPAddressesOnRestart ) ;
191194 TryGetConfig < bool > ( "ClearFailedLoginsOnSuccessfulLogin" , ref clearFailedLoginsOnSuccessfulLogin ) ;
192195 TryGetConfig < bool > ( "ProcessInternalIPAddresses" , ref processInternalIPAddresses ) ;
196+ TryGetConfig < string > ( "TruncateUserNameChars" , ref truncateUserNameChars ) ;
197+ IPBanRegexParser . Instance . TruncateUserNameChars = truncateUserNameChars ;
193198 GetConfig < TimeSpan > ( "ExpireTime" , ref expireTime , TimeSpan . Zero , maxBanTimeSpan ) ;
194199 if ( expireTime . TotalMinutes < 1.0 )
195200 {
@@ -403,138 +408,6 @@ private void ParseFirewallBlockRules()
403408 }
404409 }
405410
406- /// <summary>
407- /// Validate a regex - returns an error otherwise empty string if success
408- /// </summary>
409- /// <param name="regex">Regex to validate, can be null or empty</param>
410- /// <param name="options">Regex options</param>
411- /// <param name="throwException">True to throw the exception instead of returning the string, false otherwise</param>
412- /// <returns>Null if success, otherwise an error string indicating the problem</returns>
413- public static string ValidateRegex ( string regex , RegexOptions options = RegexOptions . IgnoreCase | RegexOptions . CultureInvariant , bool throwException = false )
414- {
415- try
416- {
417- if ( regex != null )
418- {
419- _ = new Regex ( regex , options ) ;
420- }
421- return null ;
422- }
423- catch ( Exception ex )
424- {
425- if ( throwException )
426- {
427- throw ;
428- }
429- return ex . Message ;
430- }
431- }
432-
433- private static readonly Dictionary < string , Regex > regexCacheCompiled = new ( ) ;
434- private static readonly Dictionary < string , Regex > regexCacheNotCompiled = new ( ) ;
435-
436- /// <summary>
437- /// Get a regex from text
438- /// </summary>
439- /// <param name="text">Text</param>
440- /// <param name="multiline">Whether to use multi-line regex, default is false which is single line</param>
441- /// <returns>Regex or null if text is null or whitespace</returns>
442- public static Regex ParseRegex ( string text , bool multiline = false )
443- {
444- const int maxCacheSize = 200 ;
445-
446- text = ( text ?? string . Empty ) . Trim ( ) ;
447- if ( text . Length == 0 )
448- {
449- return null ;
450- }
451-
452- string [ ] lines = text . Split ( '\n ' , StringSplitOptions . TrimEntries | StringSplitOptions . RemoveEmptyEntries ) ;
453- StringBuilder sb = new ( ) ;
454- foreach ( string line in lines )
455- {
456- sb . Append ( line ) ;
457- }
458- RegexOptions options = RegexOptions . IgnoreCase | RegexOptions . CultureInvariant | RegexOptions . Compiled ;
459- if ( multiline )
460- {
461- options |= RegexOptions . Multiline ;
462- }
463- string sbText = sb . ToString ( ) ;
464- string cacheKey = ( ( uint ) options ) . ToString ( "X8" ) + ":" + sbText ;
465-
466- // allow up to maxCacheSize compiled dynamic regular expression, with minimal config changes/reload, this should last the lifetime of an app
467- lock ( regexCacheCompiled )
468- {
469- if ( regexCacheCompiled . TryGetValue ( cacheKey , out Regex value ) )
470- {
471- return value ;
472- }
473- else if ( regexCacheCompiled . Count < maxCacheSize )
474- {
475- value = new Regex ( sbText , options ) ;
476- regexCacheCompiled . Add ( cacheKey , value ) ;
477- return value ;
478- }
479- }
480-
481- // have to fall-back to non-compiled regex to avoid run-away memory usage
482- try
483- {
484- lock ( regexCacheNotCompiled )
485- {
486- if ( regexCacheNotCompiled . TryGetValue ( cacheKey , out Regex value ) )
487- {
488- return value ;
489- }
490-
491- // strip compiled flag
492- options &= ( ~ RegexOptions . Compiled ) ;
493- value = new Regex ( sbText , options ) ;
494- regexCacheNotCompiled . Add ( cacheKey , value ) ;
495- return value ;
496- }
497- }
498- finally
499- {
500- // clear non-compield regex cache if it exceeds max size
501- lock ( regexCacheNotCompiled )
502- {
503- if ( regexCacheNotCompiled . Count > maxCacheSize )
504- {
505- regexCacheNotCompiled . Clear ( ) ;
506- }
507- }
508- }
509- }
510-
511- /// <summary>
512- /// Clean a multi-line string to make it more readable
513- /// </summary>
514- /// <param name="text">Multi-line string</param>
515- /// <returns>Cleaned multi-line string</returns>
516- public static string CleanMultilineString ( string text )
517- {
518- text = ( text ?? string . Empty ) . Trim ( ) ;
519- if ( text . Length == 0 )
520- {
521- return string . Empty ;
522- }
523-
524- string [ ] lines = text . Split ( '\n ' , StringSplitOptions . RemoveEmptyEntries ) ;
525- StringBuilder sb = new ( ) ;
526- foreach ( string line in lines )
527- {
528- string trimmedLine = line . Trim ( ) ;
529- if ( trimmedLine . Length != 0 )
530- {
531- sb . Append ( trimmedLine ) ;
532- sb . Append ( '\n ' ) ;
533- }
534- }
535- return sb . ToString ( ) . Trim ( ) ;
536- }
537-
538411 /// <inheritdoc />
539412 public override string ToString ( )
540413 {
@@ -1005,6 +878,11 @@ appSettingsOverride is not null &&
1005878 /// </summary>
1006879 public IIPBanFilter BlacklistFilter => blacklistFilter ;
1007880
881+ /// <summary>
882+ /// Characters to truncate user names at, empty for no truncation
883+ /// </summary>
884+ public string TruncateUserNameChars { get { return truncateUserNameChars ; } }
885+
1008886 /// <summary>
1009887 /// White list user names. Any user name found not in the list is banned, unless the list is empty, in which case no checking is done.
1010888 /// If not empty, Any user name within 'UserNameWhitelistMinimumEditDistance' in the config is also not banned.
0 commit comments