-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathUnknownItemsWindowController.m
338 lines (258 loc) · 8.42 KB
/
UnknownItemsWindowController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
//
// UnknownItemsWindowController.m
// KnockKnock
//
// Created by Patrick Wardle on 1/1/25
//
#define COL_ENABLED 0x0
#define COL_RESULT 0x1
#define COL_PATH 0x2
#import "Consts.h"
#import "Utilities.h"
#import "AppDelegate.h"
#import "UnknownItemsWindowController.h"
@implementation UnknownItemsWindowController
@synthesize items;
@synthesize submit;
@synthesize results;
@synthesize tableView;
@synthesize selections;
-(void)windowDidLoad {
//super
[super windowDidLoad];
//grab last column
NSTableColumn *lastColumn = self.tableView.tableColumns.lastObject;
//set its resizing mask
// expand as table does too
lastColumn.resizingMask = NSTableColumnAutoresizingMask;
//init
results = [NSMutableDictionary dictionary];
selections = [NSMutableDictionary dictionary];
//make first responder
[self.window makeFirstResponder:self.submit];
return;
}
//table delegate
// ->return number of commands
-(NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return self.items.count;
}
//table delegate method
// ->return cell for row
-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
//command
File* file = nil;
//column index
NSUInteger index = 0;
//table cell
NSTableCellView* cell = nil;
//check box
NSButton* button = nil;
//get existing cell
cell = [self.tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
//grab index
index = [[self.tableView tableColumns] indexOfObject:tableColumn];
//get item for row
file = self.items[row];
//handle column specific logic
switch(index)
{
//logic for 'enabled' column
case COL_ENABLED:
//grab checkbox
button = (NSButton*)[cell viewWithTag:1001];
if(button) {
//set the state
NSNumber* savedState = self.selections[@(row)];
button.state = savedState ? savedState.integerValue : NSControlStateValueOn;
//add target & action
button.target = self;
button.action = @selector(toggleTest:);
}
break;
case COL_RESULT:
{
//result
NSDictionary* result = self.results[@(row)];
//reset/hide text
cell.textField.hidden = YES;
cell.textField.stringValue = @"";
//hide button
button = (NSButton*)[cell viewWithTag:1001];
if(button) {
button.hidden = YES;
}
//display non-200 HTTP OK codes
if( (nil != result) &&
(200 != (long)[(NSHTTPURLResponse *)result[VT_HTTP_RESPONSE] statusCode]) )
{
//add
cell.textField.stringValue = [NSString stringWithFormat:@"Error: %ld", (long)[(NSHTTPURLResponse *)result[VT_HTTP_RESPONSE] statusCode]];
//show
cell.textField.hidden = NO;
}
//otherwise
// show button w/ link VT report
else if(nil != result[@"sha256"])
{
//hide text
cell.textField.hidden = YES;
//get button
button = (NSButton*)[cell viewWithTag:1001];
if(button) {
//configure
button.target = self;
button.action = @selector(viewReport:);
button.hidden = NO;
}
}
break;
}
//logic for 'path' column
case COL_PATH:
//set path
cell.textField.stringValue = file.path;
break;
default:
break;
}
return cell;
}
//invoked when user clicks 'View'
-(IBAction)viewReport:(NSButton*)sender {
//get row
NSInteger row = [self.tableView rowForView:sender];
if (row != -1) {
//get result
NSDictionary* result = self.results[@(row)];
//build report to URL
NSURL* report = [NSURL URLWithString:[NSString stringWithFormat:@"https://www.virustotal.com/gui/file/%@", result[@"sha256"]]];
//open (in browser)
[[NSWorkspace sharedWorkspace] openURL:report];
}
return;
}
//automatically invoked when user checks/unchecks checkbox in row
// enable/disable command state, plus handle some other button logic
-(IBAction)toggleTest:(NSButton*)sender
{
NSInteger row = [self.tableView rowForView:sender];
if (row >= 0) {
self.selections[@(row)] = @(sender.state);
}
//(re)enable submit button
if(NSOnState == ((NSButton*)(sender)).state)
{
self.submit.enabled = YES;
}
//nothing selected?
// disable submit button
else
{
//disable
self.submit.enabled = ![self allUnchecked];
}
return;
}
//checks if all items are unchecked
-(BOOL)allUnchecked {
NSButton* button = nil;
NSTableCellView* cell = nil;
//check each/all
for (NSInteger row = 0; row < self.tableView.numberOfRows; row++) {
cell = [self.tableView viewAtColumn:0 row:row makeIfNecessary:NO];
if(cell) {
button = [cell viewWithTag:1001];
if (button && (NSOnState == button.state)) {
return NO;
}
}
}
return YES;
}
//submit items to VT
-(IBAction)submit:(id)sender
{
//item
File* item = nil;
//submitted items
__block NSUInteger submittedItems = 0;
//VT object
VirusTotal* vtObj = nil;
//result (from VT)
__block NSDictionary* result = nil;
//button
NSButton* button = nil;
//cell
NSTableCellView* cell = nil;
//alloc
vtObj = [[VirusTotal alloc] init];
//disable
self.submit.enabled = NO;
//show activity indicator
self.activityIndicator.hidden = NO;
//start spinner
[self.activityIndicator startAnimation:nil];
//submit all selected items
for(NSInteger row = 0; row < self.tableView.numberOfRows; row++) {
//grab cell
cell = [self.tableView viewAtColumn:0 row:row makeIfNecessary:NO];
if(!cell) {
continue;
}
//get button
button = [cell viewWithTag:1001];
if (!button || (NSOnState != button.state)) {
continue;
}
//extract item
item = self.items[row];
//skip non-file items
if(YES != [item isKindOfClass:[File class]])
{
//skip
continue;
}
//skip item's without hashes
// ...not sure how this could ever happen
if(nil == ((File*)item).hashes[KEY_HASH_SHA1])
{
//skip
continue;
}
//set status
self.statusLabel.stringValue = [NSString stringWithFormat:NSLocalizedString(@"Submitting '%@'", @"Submitting '%@'"), ((File*)item).name];
//inc
submittedItems++;
//submit in background
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//nap
sleep(0.5);
//submit file to VT
result = [vtObj submit:(File*)item];
//save results
self.results[@(row)] = result;
//reload table
dispatch_async(dispatch_get_main_queue(), ^{
//dec
submittedItems--;
//reload
[self.tableView reloadData];
//done?
if(0 == submittedItems)
{
//(re)enable
self.submit.enabled = YES;
//stop spinner
[self.activityIndicator stopAnimation:nil];
//set status
self.statusLabel.stringValue = NSLocalizedString(@"Submissions complete!", @"Submissions complete!");
}
});
});
}
return;
}
@end