Skip to content

Commit

Permalink
fileinfo: add --filter-inclusive (#1388)
Browse files Browse the repository at this point in the history
* filter inclusive

* remove print method

* update help
  • Loading branch information
tburgin authored Jul 9, 2024
1 parent 476cd21 commit 348ff8c
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 24 deletions.
66 changes: 42 additions & 24 deletions Source/santactl/Commands/SNTCommandFileInfo.m
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ @interface SNTCommandFileInfo : SNTCommand <SNTCommandProtocol>
@property(nonatomic) BOOL recursive;
@property(nonatomic) BOOL jsonOutput;
@property(nonatomic) BOOL bundleInfo;
@property(nonatomic) BOOL filterInclusive;
@property(nonatomic) NSNumber *certIndex;
@property(nonatomic, copy) NSArray<NSString *> *outputKeyList;
@property(nonatomic, copy) NSDictionary<NSString *, NSRegularExpression *> *outputFilters;
Expand Down Expand Up @@ -189,6 +190,10 @@ + (NSString *)longHelpText {
@" case-insensitive regular expression which must match anywhere in\n"
@" the keyed property value for the file's info to be displayed.\n"
@" You may specify multiple filters by repeating this flag.\n"
@" If multiple filters are specified, any match will display the\n"
@" file.\n"
@" --filter-inclusive: If multiple filters are specified, they must all match\n"
@" for the file to be displayed.\n"
@" --bundleinfo: If the file is part of a bundle, will also display bundle\n"
@" hash information and hashes of all bundle executables.\n"
@" Incompatible with --recursive and --cert-index.\n"
Expand Down Expand Up @@ -654,8 +659,29 @@ - (void)recurseAtPath:(NSString *)path {
[operationQueue waitUntilAllOperationsAreFinished];
}

- (BOOL)shouldOutputValueToDictionary:(NSMutableDictionary *)outputDict
valueForKey:(NSString * (^)(NSString *key))valueForKey {
if (self.outputFilters.count == 0) return YES;

int matches = 0;
for (NSString *key in self.outputFilters) {
NSString *value = valueForKey(key);
NSRegularExpression *regex = self.outputFilters[key];
if (![regex firstMatchInString:value options:0 range:NSMakeRange(0, value.length)]) continue;
// If this is a value we want to show, store it in the output dictionary.
// This does a linear search on an array, but it's a small array.
if (outputDict && value.length && [self.outputKeyList containsObject:key]) {
outputDict[key] = value;
}
++matches;
}

return self.filterInclusive ? matches == self.outputFilters.count : matches > 0;
}

// Prints out the info for a single (non-directory) file. Which info is printed is controlled
// by the keys in self.outputKeyList.
// TODO: Refactor so this method is testable.
- (void)printInfoForFile:(NSString *)path {
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:path];
if (!fileInfo) {
Expand Down Expand Up @@ -689,41 +715,31 @@ - (void)printInfoForFile:(NSString *)path {
NSDictionary *cert = signingChain[index];

// Check if we should skip over this item based on outputFilters.
BOOL filterMatch = self.outputFilters.count == 0;
for (NSString *key in self.outputFilters) {
NSString *value = cert[key] ?: @"";
NSRegularExpression *regex = self.outputFilters[key];
if (![regex firstMatchInString:value options:0 range:NSMakeRange(0, value.length)]) continue;
filterMatch = YES;
break;
BOOL shouldOutput = [self shouldOutputValueToDictionary:nil
valueForKey:^NSString *(NSString *key) {
return cert[key] ?: @"";
}];
if (!shouldOutput) {
return;
}

if (!filterMatch) return;

// Filter out the info we want now, in case JSON output
for (NSString *key in self.outputKeyList) {
outputDict[key] = cert[key];
}
} else {
// Check if we should skip over this item based on outputFilters. We do this before collecting
// Check if we should skip over this item based on outputFilters. We do this before collecting
// output info because there's a chance that we can bail out early if a filter doesn't match.
// However we also don't want to recompute info, so we save any values that we plan to show.
BOOL filterMatch = self.outputFilters.count == 0;
for (NSString *key in self.outputFilters) {
NSString *value = self.propertyMap[key](self, fileInfo) ?: @"";
NSRegularExpression *regex = self.outputFilters[key];
if (![regex firstMatchInString:value options:0 range:NSMakeRange(0, value.length)]) continue;
// If this is a value we want to show, store it in the output dictionary.
// This does a linear search on an array, but it's a small array.
if (value.length && [self.outputKeyList containsObject:key]) {
outputDict[key] = value;
}
filterMatch = YES;
break;
BOOL shouldOutput =
[self shouldOutputValueToDictionary:outputDict
valueForKey:^NSString *(NSString *key) {
return self.propertyMap[key](self, fileInfo) ?: @"";
}];
if (!shouldOutput) {
return;
}

if (!filterMatch) return;

// Then fill the outputDict with the rest of the missing values.
for (NSString *key in self.outputKeyList) {
if (outputDict[key]) continue; // ignore keys that we've already set due to a filter
Expand Down Expand Up @@ -896,6 +912,8 @@ - (NSArray *)parseArguments:(NSArray<NSString *> *)arguments {
@"\n--bundleinfo is incompatible with --recursive and --cert-index"];
}
self.bundleInfo = YES;
} else if ([arg caseInsensitiveCompare:@"--filter-inclusive"] == NSOrderedSame) {
self.filterInclusive = YES;
} else {
[paths addObject:arg];
}
Expand Down
7 changes: 7 additions & 0 deletions Source/santactl/Commands/SNTCommandFileInfoTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ @interface SNTCommandFileInfo : NSObject
typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
@property(nonatomic) BOOL recursive;
@property(nonatomic) BOOL jsonOutput;
@property(nonatomic) BOOL filterInclusive;
@property(nonatomic) NSNumber *certIndex;
@property(nonatomic, copy) NSArray<NSString *> *outputKeyList;
@property(nonatomic) NSDictionary<NSString *, SNTAttributeBlock> *propertyMap;
Expand Down Expand Up @@ -250,4 +251,10 @@ - (void)testCodeSignedDefault {
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
}

- (void)testParseArgumentsFilterInclusiveTrue {
NSArray *filePaths = [self.cfi parseArguments:@[ @"--filter-inclusive", @"/usr/bin/yes" ]];
XCTAssertTrue(self.cfi.filterInclusive);
XCTAssertTrue([filePaths containsObject:@"/usr/bin/yes"]);
}

@end

0 comments on commit 348ff8c

Please sign in to comment.