diff options
Diffstat (limited to 'Source/SPTableFilterParser.m')
-rw-r--r-- | Source/SPTableFilterParser.m | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/Source/SPTableFilterParser.m b/Source/SPTableFilterParser.m new file mode 100644 index 00000000..96ba8368 --- /dev/null +++ b/Source/SPTableFilterParser.m @@ -0,0 +1,225 @@ +// +// SPTableFilterParser.m +// sequel-pro +// +// Created by Max Lohrmann on 22.04.15. +// Relocated from existing files. Previous copyright applies. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <https://github.com/sequelpro/sequelpro> + +#import "SPTableFilterParser.h" +#import "RegexKitLite.h" + +@interface SPTableFilterParser (Private) ++ (NSString *)escapeFilterArgument:(NSString *)argument againstClause:(NSString *)clause; +@end + +@implementation SPTableFilterParser + +@synthesize suppressLeadingTablePlaceholder = suppressLeadingTablePlaceholder; +@synthesize caseSensitive = caseSensitive; +@synthesize currentField = _currentField; +@synthesize argument = _argument; +@synthesize firstBetweenArgument = _firstBetweenArgument; +@synthesize secondBetweenArgument = _secondBetweenArgument; +@synthesize numberOfArguments = numberOfArguments; +@synthesize clause = _clause; + +- (id)initWithFilterClause:(NSString *)filter numberOfArguments:(NSUInteger)numArgs +{ + self = [super init]; + if (self) { + numberOfArguments = numArgs; + _clause = [filter copy]; + } + return self; +} + +- (void)dealloc +{ + SPClear(_clause); + + [self setCurrentField:nil]; + [self setArgument:nil]; + [self setFirstBetweenArgument:nil]; + [self setSecondBetweenArgument:nil]; + + [super dealloc]; +} + +- (NSString *)filterString +{ + NSString *filterString; + + // argument if Filter requires only one argument + NSMutableString *argument = [[NSMutableString alloc] initWithString:_argument]; + + // If the filter field is empty and the selected filter does not require + // only one argument, then no filtering is required - return nil. + if (![argument length] && numberOfArguments == 1) { + [argument release]; + return nil; + } + + // arguments if Filter requires two arguments + NSMutableString *firstBetweenArgument = [[NSMutableString alloc] initWithString:_firstBetweenArgument]; + NSMutableString *secondBetweenArgument = [[NSMutableString alloc] initWithString:_secondBetweenArgument]; + + // If filter requires two arguments and either of the argument fields are empty + // return nil. + if (numberOfArguments == 2) { + if (([firstBetweenArgument length] == 0) || ([secondBetweenArgument length] == 0)) { + [argument release]; + [firstBetweenArgument release]; + [secondBetweenArgument release]; + return nil; + } + } + + // Retrieve actual WHERE clause + NSMutableString *clause = [[NSMutableString alloc] init]; + [clause setString:_clause]; + + [clause replaceOccurrencesOfRegex:@"(?<!\\\\)\\$BINARY " withString:(caseSensitive) ? @"BINARY " : @""]; + [clause flushCachedRegexData]; + [clause replaceOccurrencesOfRegex:@"(?<!\\\\)\\$CURRENT_FIELD" withString:(_currentField) ? [_currentField backtickQuotedString] : @""]; + [clause flushCachedRegexData]; + + // Escape % sign for format insertion ie if number of arguments is greater than 0 + if(numberOfArguments > 0) + [clause replaceOccurrencesOfRegex:@"%" withString:@"%%"]; + [clause flushCachedRegexData]; + + // Replace placeholder ${} by %@ + NSRange matchedRange; + NSString *re = @"(?<!\\\\)\\$\\{.*?\\}"; + if([clause isMatchedByRegex:re]) { + while([clause isMatchedByRegex:re]) { + matchedRange = [clause rangeOfRegex:re]; + [clause replaceCharactersInRange:matchedRange withString:@"%@"]; + [clause flushCachedRegexData]; + } + } + + // Check number of placeholders and given 'NumberOfArguments' + if([clause replaceOccurrencesOfString:@"%@" withString:@"%@" options:NSLiteralSearch range:NSMakeRange(0, [clause length])] != numberOfArguments) { + SPLog(@"Error while setting filter string. “NumberOfArguments” differs from the number of arguments specified in “Clause”."); + NSBeep(); + [argument release]; + [firstBetweenArgument release]; + [secondBetweenArgument release]; + [clause release]; + return nil; + } + + // Construct the filter string according the required number of arguments + + if(suppressLeadingTablePlaceholder) { + if (numberOfArguments == 2) { + filterString = [NSString stringWithFormat:clause, + [[self class] escapeFilterArgument:firstBetweenArgument againstClause:clause], + [[self class] escapeFilterArgument:secondBetweenArgument againstClause:clause]]; + } else if (numberOfArguments == 1) { + filterString = [NSString stringWithFormat:clause, [[self class] escapeFilterArgument:argument againstClause:clause]]; + } else { + filterString = [NSString stringWithString:clause]; + if(numberOfArguments > 2) { + SPLog(@"Filter with more than 2 arguments is not yet supported."); + NSBeep(); + } + } + } else { + if (numberOfArguments == 2) { + filterString = [NSString stringWithFormat:@"%@ %@", + [_currentField backtickQuotedString], + [NSString stringWithFormat:clause, + [[self class] escapeFilterArgument:firstBetweenArgument againstClause:clause], + [[self class] escapeFilterArgument:secondBetweenArgument againstClause:clause]]]; + } else if (numberOfArguments == 1) { + filterString = [NSString stringWithFormat:@"%@ %@", + [_currentField backtickQuotedString], + [NSString stringWithFormat:clause, [[self class] escapeFilterArgument:argument againstClause:clause]]]; + } else { + filterString = [NSString stringWithFormat:@"%@ %@", + [_currentField backtickQuotedString], clause]; + if(numberOfArguments > 2) { + SPLog(@"Filter with more than 2 arguments is not yet supported."); + NSBeep(); + } + } + } + + [argument release]; + [firstBetweenArgument release]; + [secondBetweenArgument release]; + [clause release]; + + // Return the filter string + return filterString; +} + +/** + * Escape argument by looking for used quoting strings in a clause. Attempt to + * be smart - use a single escape for most clauses, doubling up for LIKE clauses. + * Also attempt to not escape what look like common escape sequences - \n, \r, \t. + * + * @param argument The to be used filter argument which should be be escaped + * + * @param clause The entire WHERE filter clause + * + */ ++ (NSString *)escapeFilterArgument:(NSString *)argument againstClause:(NSString *)clause +{ + BOOL clauseIsLike = [clause isMatchedByRegex:@"(?i)\\blike\\b.*?%(?!@)"]; + NSString *recognizedEscapeSequences, *escapeSequence, *regexTerm; + NSMutableString *arg = [argument mutableCopy]; + + // Determine the character set not to escape slashes before, and the escape depth + if (clauseIsLike) { + recognizedEscapeSequences = @"nrt_%"; + escapeSequence = @"\\\\\\\\\\\\\\\\"; + } else { + recognizedEscapeSequences = @"nrt"; + escapeSequence = @"\\\\\\\\"; + } + regexTerm = [NSString stringWithFormat:@"(\\\\)(?![%@])", recognizedEscapeSequences]; + + // Escape slashes appropriately + [arg replaceOccurrencesOfRegex:regexTerm withString:escapeSequence]; + [arg flushCachedRegexData]; + + // Get quote sign for escaping - this should work for 99% of all cases + NSString *quoteSign = [clause stringByMatching:@"([\"'])[^\\1]*?%@[^\\1]*?\\1" capture:1L]; + + // Escape argument + if(quoteSign != nil && [quoteSign length] == 1) { + [arg replaceOccurrencesOfRegex:[NSString stringWithFormat:@"(%@)", quoteSign] withString:@"\\\\$1"]; + [arg flushCachedRegexData]; + } + + return [arg autorelease]; +} + + +@end |