Просмотр исходного кода

Add network host blacklisting in UI

Make networkRequestHostBlacklist a mutable array
Tanner Bennett лет назад: 6
Родитель
Сommit
740daab55b

+ 1 - 1
Classes/Manager/FLEXManager+Networking.h

@@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN
 /// Requests whose host ends with one of the blacklisted entries in this array will be not be recorded (eg. google.com).
 /// Wildcard or subdomain entries are not required (eg. google.com will match any subdomain under google.com).
 /// Useful to remove requests that are typically noisy, such as analytics requests that you aren't interested in tracking.
-@property (nonatomic, copy) NSArray<NSString *> *networkRequestHostBlacklist;
+@property (nonatomic) NSMutableArray<NSString *> *networkRequestHostBlacklist;
 
 /// Sets custom viewer for specific content type.
 /// @param contentType Mime type like application/json

+ 19 - 3
Classes/Manager/FLEXManager+Networking.m

@@ -10,9 +10,24 @@
 #import "FLEXManager+Private.h"
 #import "FLEXNetworkObserver.h"
 #import "FLEXNetworkRecorder.h"
+#import "FLEXObjectExplorerFactory.h"
 
 @implementation FLEXManager (Networking)
 
++ (void)load {
+    // Register array/dictionary viewer for JSON responses
+    [self.sharedManager setCustomViewerForContentType:@"application/json"
+        viewControllerFutureBlock:^UIViewController *(NSData *data) {
+            id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
+            if (jsonObject) {
+                return [FLEXObjectExplorerFactory explorerViewControllerForObject:jsonObject];
+            }
+        
+            return nil;
+        }
+    ];
+}
+
 - (BOOL)isNetworkDebuggingEnabled {
     return FLEXNetworkObserver.isEnabled;
 }
@@ -29,15 +44,16 @@
     FLEXNetworkRecorder.defaultRecorder.responseCacheByteLimit = networkResponseCacheByteLimit;
 }
 
-- (NSArray<NSString *> *)networkRequestHostBlacklist {
+- (NSMutableArray<NSString *> *)networkRequestHostBlacklist {
     return FLEXNetworkRecorder.defaultRecorder.hostBlacklist;
 }
 
-- (void)setNetworkRequestHostBlacklist:(NSArray<NSString *> *)networkRequestHostBlacklist {
+- (void)setNetworkRequestHostBlacklist:(NSMutableArray<NSString *> *)networkRequestHostBlacklist {
     FLEXNetworkRecorder.defaultRecorder.hostBlacklist = networkRequestHostBlacklist;
 }
 
-- (void)setCustomViewerForContentType:(NSString *)contentType viewControllerFutureBlock:(FLEXCustomContentViewerFuture)viewControllerFutureBlock {
+- (void)setCustomViewerForContentType:(NSString *)contentType
+            viewControllerFutureBlock:(FLEXCustomContentViewerFuture)viewControllerFutureBlock {
     NSParameterAssert(contentType.length);
     NSParameterAssert(viewControllerFutureBlock);
     NSAssert(NSThread.isMainThread, @"This method must be called from the main thread.");

+ 16 - 4
Classes/Network/FLEXNetworkMITMViewController.m

@@ -14,7 +14,7 @@
 #import "FLEXNetworkObserver.h"
 #import "FLEXNetworkTransactionTableViewCell.h"
 #import "FLEXNetworkTransactionDetailTableViewController.h"
-#import "FLEXNetworkSettingsTableViewController.h"
+#import "FLEXNetworkSettingsController.h"
 #import "FLEXGlobalsViewController.h"
 #import "UIBarButtonItem+FLEX.h"
 #import "FLEXResources.h"
@@ -110,7 +110,7 @@
 #pragma mark Button Actions
 
 - (void)settingsButtonTapped:(id)sender {
-    UIViewController *settings = [FLEXNetworkSettingsTableViewController new];
+    UIViewController *settings = [FLEXNetworkSettingsController new];
     settings.navigationItem.rightBarButtonItem = FLEXBarButtonItemSystem(
         Done, self, @selector(settingsViewControllerDoneTapped:)
     );
@@ -393,6 +393,7 @@
 #if FLEX_AT_LEAST_IOS13_SDK
 
 - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
+    NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
     return [UIContextMenuConfiguration
         configurationWithIdentifier:nil
         previewProvider:nil
@@ -402,14 +403,25 @@
                 image:nil
                 identifier:nil
                 handler:^(__kindof UIAction *action) {
-                    NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
                     UIPasteboard.generalPasteboard.string = request.URL.absoluteString ?: @"";
                 }
             ];
+            UIAction *blacklist = [UIAction
+                actionWithTitle:[NSString stringWithFormat:@"Blacklist '%@'", request.URL.host]
+                image:nil
+                identifier:nil
+                handler:^(__kindof UIAction *action) {
+                    NSMutableArray *blacklist =  FLEXNetworkRecorder.defaultRecorder.hostBlacklist;
+                    [blacklist addObject:request.URL.host];
+                    [FLEXNetworkRecorder.defaultRecorder clearBlacklistedTransactions];
+                    [FLEXNetworkRecorder.defaultRecorder synchronizeBlacklist];
+                    [self tryUpdateTransactions];
+                }
+            ];
             return [UIMenu
                 menuWithTitle:@"" image:nil identifier:nil
                 options:UIMenuOptionsDisplayInline
-                children:@[copy]
+                children:@[copy, blacklist]
             ];
         }
     ];

+ 12 - 3
Classes/Network/FLEXNetworkRecorder.h

@@ -24,10 +24,17 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
 /// Defaults to 25 MB if never set. Values set here are persisted across launches of the app.
 @property (nonatomic) NSUInteger responseCacheByteLimit;
 
-/// If NO, the recorder not cache will not cache response for content types with an "image", "video", or "audio" prefix.
+/// If NO, the recorder not cache will not cache response for content types
+/// with an "image", "video", or "audio" prefix.
 @property (nonatomic) BOOL shouldCacheMediaResponses;
 
-@property (nonatomic, copy) NSArray<NSString *> *hostBlacklist;
+@property (nonatomic) NSMutableArray<NSString *> *hostBlacklist;
+
+/// Call this after adding to or setting the \c hostBlacklist to remove blacklisted transactions
+- (void)clearBlacklistedTransactions;
+
+/// Call this to save the blacklist to the disk to be loaded next time
+- (void)synchronizeBlacklist;
 
 
 // Accessing recorded network activity
@@ -45,7 +52,9 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
 // Recording network activity
 
 /// Call when app is about to send HTTP request.
-- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID request:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;
+- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID
+                                     request:(NSURLRequest *)request
+                            redirectResponse:(NSURLResponse *)redirectResponse;
 
 /// Call when HTTP response is available.
 - (void)recordResponseReceivedWithRequestID:(NSString *)requestID response:(NSURLResponse *)response;

+ 33 - 9
Classes/Network/FLEXNetworkRecorder.m

@@ -11,6 +11,7 @@
 #import "FLEXNetworkTransaction.h"
 #import "FLEXUtility.h"
 #import "FLEXResources.h"
+#import "NSUserDefaults+FLEX.h"
 
 NSString *const kFLEXNetworkRecorderNewTransactionNotification = @"kFLEXNetworkRecorderNewTransactionNotification";
 NSString *const kFLEXNetworkRecorderTransactionUpdatedNotification = @"kFLEXNetworkRecorderTransactionUpdatedNotification";
@@ -23,7 +24,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
 
 @property (nonatomic) NSCache *responseCache;
 @property (nonatomic) NSMutableArray<FLEXNetworkTransaction *> *orderedTransactions;
-@property (nonatomic) NSMutableDictionary<NSString *, FLEXNetworkTransaction *> *networkTransactionsForRequestIdentifiers;
+@property (nonatomic) NSMutableDictionary<NSString *, FLEXNetworkTransaction *> *requestIDsToTransactions;
 @property (nonatomic) dispatch_queue_t queue;
 
 @end
@@ -37,12 +38,14 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
         NSUInteger responseCacheLimit = [[NSUserDefaults.standardUserDefaults
             objectForKey:kFLEXNetworkRecorderResponseCacheLimitDefaultsKey] unsignedIntegerValue
         ];
+        
         // Default to 25 MB max. The cache will purge earlier if there is memory pressure.
         self.responseCache.totalCostLimit = responseCacheLimit ?: 25 * 1024 * 1024;
         [self.responseCache setTotalCostLimit:responseCacheLimit];
         
         self.orderedTransactions = [NSMutableArray new];
-        self.networkTransactionsForRequestIdentifiers = [NSMutableDictionary new];
+        self.requestIDsToTransactions = [NSMutableDictionary new];
+        self.hostBlacklist = NSUserDefaults.standardUserDefaults.flex_networkHostBlacklist.mutableCopy;
 
         // Serial queue used because we use mutable objects that are not thread safe
         self.queue = dispatch_queue_create("com.flex.FLEXNetworkRecorder", DISPATCH_QUEUE_SERIAL);
@@ -91,12 +94,33 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
     dispatch_async(self.queue, ^{
         [self.responseCache removeAllObjects];
         [self.orderedTransactions removeAllObjects];
-        [self.networkTransactionsForRequestIdentifiers removeAllObjects];
+        [self.requestIDsToTransactions removeAllObjects];
         
         [self notify:kFLEXNetworkRecorderTransactionsClearedNotification transaction:nil];
     });
 }
 
+- (void)clearBlacklistedTransactions {
+    dispatch_sync(self.queue, ^{
+        self.orderedTransactions = ({
+            [self.orderedTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *ta, NSUInteger idx) {
+                NSString *host = ta.request.URL.host;
+                for (NSString *blacklisted in self.hostBlacklist) {
+                    if ([host hasSuffix:blacklisted]) {
+                        return NO;
+                    }
+                }
+                
+                return YES;
+            }];
+        });
+    });
+}
+
+- (void)synchronizeBlacklist {
+    NSUserDefaults.standardUserDefaults.flex_networkHostBlacklist = self.hostBlacklist;
+}
+
 #pragma mark - Network Events
 
 - (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID
@@ -123,7 +147,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
         transaction.startTime = startDate;
 
         [self.orderedTransactions insertObject:transaction atIndex:0];
-        [self.networkTransactionsForRequestIdentifiers setObject:transaction forKey:requestID];
+        [self.requestIDsToTransactions setObject:transaction forKey:requestID];
         transaction.transactionState = FLEXNetworkTransactionStateAwaitingResponse;
 
         [self postNewTransactionNotificationWithTransaction:transaction];
@@ -135,7 +159,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
     NSDate *responseDate = [NSDate date];
 
     dispatch_async(self.queue, ^{
-        FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
+        FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
         if (!transaction) {
             return;
         }
@@ -150,7 +174,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
 
 - (void)recordDataReceivedWithRequestID:(NSString *)requestID dataLength:(int64_t)dataLength {
     dispatch_async(self.queue, ^{
-        FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
+        FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
         if (!transaction) {
             return;
         }
@@ -164,7 +188,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
     NSDate *finishedDate = [NSDate date];
 
     dispatch_async(self.queue, ^{
-        FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
+        FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
         if (!transaction) {
             return;
         }
@@ -223,7 +247,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
 
 - (void)recordLoadingFailedWithRequestID:(NSString *)requestID error:(NSError *)error {
     dispatch_async(self.queue, ^{
-        FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
+        FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
         if (!transaction) {
             return;
         }
@@ -238,7 +262,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
 
 - (void)recordMechanism:(NSString *)mechanism forRequestID:(NSString *)requestID {
     dispatch_async(self.queue, ^{
-        FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
+        FLEXNetworkTransaction *transaction = self.requestIDsToTransactions[requestID];
         if (!transaction) {
             return;
         }

+ 12 - 0
Classes/Network/FLEXNetworkSettingsController.h

@@ -0,0 +1,12 @@
+//
+//  FLEXNetworkSettingsController.h
+//  FLEXInjected
+//
+//  Created by Ryan Olson on 2/20/15.
+//
+
+#import "FLEXTableViewController.h"
+
+@interface FLEXNetworkSettingsController : FLEXTableViewController
+
+@end

+ 221 - 0
Classes/Network/FLEXNetworkSettingsController.m

@@ -0,0 +1,221 @@
+//
+//  FLEXNetworkSettingsController.m
+//  FLEXInjected
+//
+//  Created by Ryan Olson on 2/20/15.
+//
+
+#import "FLEXNetworkSettingsController.h"
+#import "FLEXNetworkObserver.h"
+#import "FLEXNetworkRecorder.h"
+#import "FLEXUtility.h"
+#import "FLEXTableView.h"
+#import "FLEXColor.h"
+
+@interface FLEXNetworkSettingsController () <UIActionSheetDelegate>
+@property (nonatomic) float cacheLimitValue;
+@property (nonatomic, readonly) NSString *cacheLimitCellTitle;
+
+@property (nonatomic, readonly) UISwitch *observerSwitch;
+@property (nonatomic, readonly) UISwitch *cacheMediaSwitch;
+@property (nonatomic, readonly) UISlider *cacheLimitSlider;
+@property (nonatomic) UILabel *cacheLimitLabel;
+
+@property (nonatomic) NSMutableArray<NSString *> *hostBlacklist;
+@end
+
+@implementation FLEXNetworkSettingsController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    
+    [self disableToolbar];
+    self.hostBlacklist = FLEXNetworkRecorder.defaultRecorder.hostBlacklist.mutableCopy;
+    
+    _observerSwitch = [UISwitch new];
+    _cacheMediaSwitch = [UISwitch new];
+    _cacheLimitSlider = [UISlider new];
+    
+    self.observerSwitch.on = FLEXNetworkObserver.enabled;
+    [self.observerSwitch addTarget:self
+        action:@selector(networkDebuggingToggled:)
+        forControlEvents:UIControlEventValueChanged
+    ];
+    
+    self.cacheMediaSwitch.on = FLEXNetworkRecorder.defaultRecorder.shouldCacheMediaResponses;
+    [self.cacheMediaSwitch addTarget:self
+        action:@selector(cacheMediaResponsesToggled:)
+        forControlEvents:UIControlEventValueChanged
+    ];
+    
+    [self.cacheLimitSlider addTarget:self
+        action:@selector(cacheLimitAdjusted:)
+        forControlEvents:UIControlEventValueChanged
+    ];
+    
+    UISlider *slider = self.cacheLimitSlider;
+    self.cacheLimitValue = FLEXNetworkRecorder.defaultRecorder.responseCacheByteLimit;
+    const NSUInteger fiftyMega = 50 * 1024 * 1024;
+    slider.minimumValue = 0;
+    slider.maximumValue = fiftyMega;
+    slider.value = self.cacheLimitValue;
+}
+
+- (void)setCacheLimitValue:(float)cacheLimitValue {
+    _cacheLimitValue = cacheLimitValue;
+    self.cacheLimitLabel.text = self.cacheLimitCellTitle;
+    [FLEXNetworkRecorder.defaultRecorder setResponseCacheByteLimit:cacheLimitValue];
+}
+
+- (NSString *)cacheLimitCellTitle {
+    NSInteger cacheLimit = self.cacheLimitValue;
+    NSInteger limitInMB = round(cacheLimit / (1024 * 1024));
+    return [NSString stringWithFormat:@"Cache Limit (%@ MB)", @(limitInMB)];
+}
+
+
+#pragma mark - Settings Actions
+
+- (void)networkDebuggingToggled:(UISwitch *)sender {
+    FLEXNetworkObserver.enabled = sender.isOn;
+}
+
+- (void)cacheMediaResponsesToggled:(UISwitch *)sender {
+    FLEXNetworkRecorder.defaultRecorder.shouldCacheMediaResponses = sender.isOn;
+}
+
+- (void)cacheLimitAdjusted:(UISlider *)sender {
+    self.cacheLimitValue = sender.value;
+}
+
+
+#pragma mark - Table View
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+    return self.hostBlacklist.count ? 2 : 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    switch (section) {
+        case 0: return 4;
+        case 1: return self.hostBlacklist.count;
+        default: return 0;
+    }
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+    switch (section) {
+        case 0: return @"General";
+        case 1: return @"Host Blacklist";
+        default: return nil;
+    }
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  {
+    UITableViewCell *cell = [self.tableView
+        dequeueReusableCellWithIdentifier:kFLEXDefaultCell forIndexPath:indexPath
+    ];
+    
+    cell.accessoryView = nil;
+    cell.textLabel.textColor = FLEXColor.primaryTextColor;
+    
+    switch (indexPath.section) {
+        // Settings
+        case 0: {
+            switch (indexPath.row) {
+                case 0:
+                    cell.textLabel.text = @"Network Debugging";
+                    cell.accessoryView = self.observerSwitch;
+                    break;
+                case 1:
+                    cell.textLabel.text = @"Cache Media Responses";
+                    cell.accessoryView = self.cacheMediaSwitch;
+                    break;
+                case 2:
+                    cell.textLabel.text = @"Reset Host Blacklist";
+                    cell.textLabel.textColor = tableView.tintColor;
+                    break;
+                case 3:
+                    cell.textLabel.text = self.cacheLimitCellTitle;
+                    self.cacheLimitLabel = cell.textLabel;
+                    [self.cacheLimitSlider removeFromSuperview];
+                    [cell.contentView addSubview:self.cacheLimitSlider];
+                    
+                    CGRect container = cell.contentView.frame;
+                    UISlider *slider = self.cacheLimitSlider;
+                    [slider sizeToFit];
+                    
+                    CGFloat sliderWidth = 150.f;
+                    CGFloat sliderOriginY = FLEXFloor((container.size.height - slider.frame.size.height) / 2.0);
+                    CGFloat sliderOriginX = CGRectGetMaxX(container) - sliderWidth - tableView.separatorInset.left;
+                    self.cacheLimitSlider.frame = CGRectMake(
+                        sliderOriginX, sliderOriginY, sliderWidth, slider.frame.size.height
+                    );
+                    
+                    // Make wider, keep in middle of cell, keep to trailing edge of cell
+                    self.cacheLimitSlider.autoresizingMask = ({
+                        UIViewAutoresizingFlexibleWidth |
+                        UIViewAutoresizingFlexibleLeftMargin |
+                        UIViewAutoresizingFlexibleTopMargin |
+                        UIViewAutoresizingFlexibleBottomMargin;
+                    });
+                    break;
+            }
+            
+            break;
+        }
+        
+        // Blacklist entries
+        case 1: {
+            cell.textLabel.text = self.hostBlacklist[indexPath.row];
+            break;
+        }
+        
+        default:
+            @throw NSInternalInconsistencyException;
+            break;
+    }
+
+    return cell;
+}
+
+- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)ip {
+    // Can only select the "Reset Host Blacklist" row
+    return ip.section == 0 && ip.row == 2;
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    [tableView deselectRowAtIndexPath:indexPath animated:YES];
+    
+    [FLEXAlert makeAlert:^(FLEXAlert *make) {
+        make.title(@"Reset Host Blacklist");
+        make.message(@"You cannot undo this action. Are you sure?");
+        make.button(@"Reset").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
+            self.hostBlacklist = nil;
+            [FLEXNetworkRecorder.defaultRecorder.hostBlacklist removeAllObjects];
+            [FLEXNetworkRecorder.defaultRecorder synchronizeBlacklist];
+            [self.tableView deleteSections:
+                [NSIndexSet indexSetWithIndex:1]
+            withRowAnimation:UITableViewRowAnimationAutomatic];
+        });
+        make.button(@"Cancel").cancelStyle();
+    } showFrom:self];
+}
+
+- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
+    return indexPath.section == 1;
+}
+
+- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)style
+forRowAtIndexPath:(NSIndexPath *)indexPath {
+    NSParameterAssert(style == UITableViewCellEditingStyleDelete);
+    
+    NSString *host = self.hostBlacklist[indexPath.row];
+    [self.hostBlacklist removeObjectAtIndex:indexPath.row];
+    [FLEXNetworkRecorder.defaultRecorder.hostBlacklist removeObject:host];
+    [FLEXNetworkRecorder.defaultRecorder synchronizeBlacklist];
+    
+    [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
+}
+
+@end

+ 0 - 13
Classes/Network/FLEXNetworkSettingsTableViewController.h

@@ -1,13 +0,0 @@
-//
-//  FLEXNetworkSettingsTableViewController.h
-//  FLEXInjected
-//
-//  Created by Ryan Olson on 2/20/15.
-//
-//
-
-#import <UIKit/UIKit.h>
-
-@interface FLEXNetworkSettingsTableViewController : UITableViewController
-
-@end

+ 0 - 133
Classes/Network/FLEXNetworkSettingsTableViewController.m

@@ -1,133 +0,0 @@
-//
-//  FLEXNetworkSettingsTableViewController.m
-//  FLEXInjected
-//
-//  Created by Ryan Olson on 2/20/15.
-//
-//
-
-#import "FLEXNetworkSettingsTableViewController.h"
-#import "FLEXNetworkObserver.h"
-#import "FLEXNetworkRecorder.h"
-#import "FLEXUtility.h"
-
-@interface FLEXNetworkSettingsTableViewController () <UIActionSheetDelegate>
-
-@property (nonatomic, copy) NSArray<UITableViewCell *> *cells;
-
-@property (nonatomic) UITableViewCell *cacheLimitCell;
-
-@end
-
-@implementation FLEXNetworkSettingsTableViewController
-
-- (instancetype)initWithStyle:(UITableViewStyle)style {
-    self = [super initWithStyle:UITableViewStyleGrouped];
-    if (self) {
-
-    }
-    return self;
-}
-
-- (void)viewDidLoad {
-    [super viewDidLoad];
-
-    NSMutableArray<UITableViewCell *> *mutableCells = [NSMutableArray new];
-
-    UITableViewCell *networkDebuggingCell = [self switchCellWithTitle:@"Network Debugging" toggleAction:@selector(networkDebuggingToggled:) isOn:FLEXNetworkObserver.isEnabled];
-    [mutableCells addObject:networkDebuggingCell];
-
-    UITableViewCell *cacheMediaResponsesCell = [self switchCellWithTitle:@"Cache Media Responses" toggleAction:@selector(cacheMediaResponsesToggled:) isOn:NO];
-    [mutableCells addObject:cacheMediaResponsesCell];
-
-    NSUInteger currentCacheLimit = [FLEXNetworkRecorder.defaultRecorder responseCacheByteLimit];
-    const NSUInteger fiftyMega = 50 * 1024 * 1024;
-    NSString *cacheLimitTitle = [self titleForCacheLimitCellWithValue:currentCacheLimit];
-    self.cacheLimitCell = [self sliderCellWithTitle:cacheLimitTitle changedAction:@selector(cacheLimitAdjusted:) minimum:0.0 maximum:fiftyMega initialValue:currentCacheLimit];
-    [mutableCells addObject:self.cacheLimitCell];
-
-    self.cells = mutableCells;
-}
-
-
-#pragma mark - Settings Actions
-
-- (void)networkDebuggingToggled:(UISwitch *)sender {
-    [FLEXNetworkObserver setEnabled:sender.isOn];
-}
-
-- (void)cacheMediaResponsesToggled:(UISwitch *)sender {
-    [FLEXNetworkRecorder.defaultRecorder setShouldCacheMediaResponses:sender.isOn];
-}
-
-- (void)cacheLimitAdjusted:(UISlider *)sender {
-    [FLEXNetworkRecorder.defaultRecorder setResponseCacheByteLimit:sender.value];
-    self.cacheLimitCell.textLabel.text = [self titleForCacheLimitCellWithValue:sender.value];
-}
-
-
-#pragma mark - Table View Data Source
-
-- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    return self.cells.count;
-}
-
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  {
-    return self.cells[indexPath.row];
-}
-
-
-#pragma mark - Helpers
-
-- (UITableViewCell *)switchCellWithTitle:(NSString *)title toggleAction:(SEL)toggleAction isOn:(BOOL)isOn {
-    UITableViewCell *cell = [UITableViewCell new];
-    cell.selectionStyle = UITableViewCellSelectionStyleNone;
-    cell.textLabel.text = title;
-    cell.textLabel.font = [[self class] cellTitleFont];
-
-    UISwitch *theSwitch = [UISwitch new];
-    theSwitch.on = isOn;
-    [theSwitch addTarget:self action:toggleAction forControlEvents:UIControlEventValueChanged];
-
-    CGFloat switchOriginY = round((cell.contentView.frame.size.height - theSwitch.frame.size.height) / 2.0);
-    CGFloat switchOriginX = CGRectGetMaxX(cell.contentView.frame) - theSwitch.frame.size.width - self.tableView.separatorInset.left;
-    theSwitch.frame = CGRectMake(switchOriginX, switchOriginY, theSwitch.frame.size.width, theSwitch.frame.size.height);
-    theSwitch.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
-    [cell.contentView addSubview:theSwitch];
-
-    return cell;
-}
-
-- (NSString *)titleForCacheLimitCellWithValue:(long long)cacheLimit {
-    NSInteger limitInMB = round(cacheLimit / (1024 * 1024));
-    return [NSString stringWithFormat:@"Cache Limit (%ld MB)", (long)limitInMB];
-}
-
-- (UITableViewCell *)sliderCellWithTitle:(NSString *)title changedAction:(SEL)changedAction minimum:(CGFloat)minimum maximum:(CGFloat)maximum initialValue:(CGFloat)initialValue {
-    UITableViewCell *sliderCell = [UITableViewCell new];
-    sliderCell.selectionStyle = UITableViewCellSelectionStyleNone;
-    sliderCell.textLabel.text = title;
-    sliderCell.textLabel.font = [[self class] cellTitleFont];
-
-    UISlider *slider = [UISlider new];
-    slider.minimumValue = minimum;
-    slider.maximumValue = maximum;
-    slider.value = initialValue;
-    [slider addTarget:self action:changedAction forControlEvents:UIControlEventValueChanged];
-    [slider sizeToFit];
-
-    CGFloat sliderWidth = round(sliderCell.contentView.frame.size.width * 2.0 / 5.0);
-    CGFloat sliderOriginY = round((sliderCell.contentView.frame.size.height - slider.frame.size.height) / 2.0);
-    CGFloat sliderOriginX = CGRectGetMaxX(sliderCell.contentView.frame) - sliderWidth - self.tableView.separatorInset.left;
-    slider.frame = CGRectMake(sliderOriginX, sliderOriginY, sliderWidth, slider.frame.size.height);
-    slider.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
-    [sliderCell.contentView addSubview:slider];
-
-    return sliderCell;
-}
-
-+ (UIFont *)cellTitleFont {
-    return [UIFont systemFontOfSize:14.0];
-}
-
-@end

+ 4 - 0
Classes/Utility/Categories/NSUserDefaults+FLEX.h

@@ -14,6 +14,7 @@ extern NSString * const kFLEXDefaultsiOSPersistentOSLogKey;
 extern NSString * const kFLEXDefaultsHidePropertyIvarsKey;
 extern NSString * const kFLEXDefaultsHidePropertyMethodsKey;
 extern NSString * const kFLEXDefaultsHideMethodOverridesKey;
+extern NSString * const kFLEXDefaultsNetworkHostBlacklistKey;
 
 @interface NSUserDefaults (FLEX)
 
@@ -31,4 +32,7 @@ extern NSString * const kFLEXDefaultsHideMethodOverridesKey;
 /// NO by default
 @property (nonatomic) BOOL flex_explorerShowsMethodOverrides;
 
+// Not actually stored in defaults, but written to a file
+@property (nonatomic) NSArray<NSString *> *flex_networkHostBlacklist;
+
 @end

+ 32 - 0
Classes/Utility/Categories/NSUserDefaults+FLEX.m

@@ -13,9 +13,28 @@ NSString * const kFLEXDefaultsiOSPersistentOSLogKey = @"com.flipborad.flex.enabl
 NSString * const kFLEXDefaultsHidePropertyIvarsKey = @"com.flipboard.FLEX.hide_property_ivars";
 NSString * const kFLEXDefaultsHidePropertyMethodsKey = @"com.flipboard.FLEX.hide_property_methods";
 NSString * const kFLEXDefaultsHideMethodOverridesKey = @"com.flipboard.FLEX.hide_method_overrides";
+NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.network_host_blacklist";
+
+#define FLEXDefaultsPathForFile(name) ({ \
+    NSArray *paths = NSSearchPathForDirectoriesInDomains( \
+        NSLibraryDirectory, NSUserDomainMask, NO \
+    ); \
+    [paths[0] stringByAppendingPathComponent:@"Preferences"]; \
+})
 
 @implementation NSUserDefaults (FLEX)
 
+/// @param filename the name of a plist file without any extension
+- (NSString *)flex_defaultsPathForFile:(NSString *)filename {
+    filename = [filename stringByAppendingPathExtension:@"plist"];
+    
+    NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(
+        NSLibraryDirectory, NSUserDomainMask, YES
+    );
+    NSString *preferences = [paths[0] stringByAppendingPathComponent:@"Preferences"];
+    return [preferences stringByAppendingPathComponent:filename];
+}
+
 - (void)toggleBoolForKey:(NSString *)key {
     [self setBool:![self boolForKey:key] forKey:key];
     [NSNotificationCenter.defaultCenter postNotificationName:key object:nil];
@@ -81,4 +100,17 @@ NSString * const kFLEXDefaultsHideMethodOverridesKey = @"com.flipboard.FLEX.hide
     ];
 }
 
+- (NSArray<NSString *> *)flex_networkHostBlacklist {
+    return [NSArray arrayWithContentsOfFile:[
+        self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
+    ]] ?: @[];
+}
+
+- (void)setFlex_networkHostBlacklist:(NSArray<NSString *> *)blacklist {
+    NSParameterAssert(blacklist);
+    [blacklist writeToFile:[
+        self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
+    ] atomically:YES];
+}
+
 @end

+ 8 - 8
FLEX.xcodeproj/project.pbxproj

@@ -91,8 +91,8 @@
 		3A4C95361B5B21410088C3F2 /* FLEXNetworkMITMViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94B51B5B21410088C3F2 /* FLEXNetworkMITMViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C95371B5B21410088C3F2 /* FLEXNetworkMITMViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94B61B5B21410088C3F2 /* FLEXNetworkMITMViewController.m */; };
 		3A4C95381B5B21410088C3F2 /* FLEXNetworkRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94B71B5B21410088C3F2 /* FLEXNetworkRecorder.h */; settings = {ATTRIBUTES = (Private, ); }; };
-		3A4C953A1B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94B91B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
-		3A4C953B1B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.m */; };
+		3A4C953A1B5B21410088C3F2 /* FLEXNetworkSettingsController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94B91B5B21410088C3F2 /* FLEXNetworkSettingsController.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		3A4C953B1B5B21410088C3F2 /* FLEXNetworkSettingsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsController.m */; };
 		3A4C953C1B5B21410088C3F2 /* FLEXNetworkTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BB1B5B21410088C3F2 /* FLEXNetworkTransaction.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C953D1B5B21410088C3F2 /* FLEXNetworkTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */; };
 		3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -447,8 +447,8 @@
 		3A4C94B61B5B21410088C3F2 /* FLEXNetworkMITMViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkMITMViewController.m; sourceTree = "<group>"; };
 		3A4C94B71B5B21410088C3F2 /* FLEXNetworkRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkRecorder.h; sourceTree = "<group>"; };
 		3A4C94B81B5B21410088C3F2 /* FLEXNetworkRecorder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkRecorder.m; sourceTree = "<group>"; };
-		3A4C94B91B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkSettingsTableViewController.h; sourceTree = "<group>"; };
-		3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkSettingsTableViewController.m; sourceTree = "<group>"; };
+		3A4C94B91B5B21410088C3F2 /* FLEXNetworkSettingsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkSettingsController.h; sourceTree = "<group>"; };
+		3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkSettingsController.m; sourceTree = "<group>"; };
 		3A4C94BB1B5B21410088C3F2 /* FLEXNetworkTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransaction.h; sourceTree = "<group>"; };
 		3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkTransaction.m; sourceTree = "<group>"; };
 		3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkTransactionDetailTableViewController.h; sourceTree = "<group>"; };
@@ -946,8 +946,8 @@
 				3A4C94B61B5B21410088C3F2 /* FLEXNetworkMITMViewController.m */,
 				3A4C94B71B5B21410088C3F2 /* FLEXNetworkRecorder.h */,
 				3A4C94B81B5B21410088C3F2 /* FLEXNetworkRecorder.m */,
-				3A4C94B91B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.h */,
-				3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.m */,
+				3A4C94B91B5B21410088C3F2 /* FLEXNetworkSettingsController.h */,
+				3A4C94BA1B5B21410088C3F2 /* FLEXNetworkSettingsController.m */,
 				3A4C94BB1B5B21410088C3F2 /* FLEXNetworkTransaction.h */,
 				3A4C94BC1B5B21410088C3F2 /* FLEXNetworkTransaction.m */,
 				3A4C94BD1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h */,
@@ -1452,7 +1452,7 @@
 				C387C87A22DFCD6A00750E58 /* FLEXCarouselCell.h in Headers */,
 				C3511B9122D7C99E0057BAB7 /* FLEXGlobalsSection.h in Headers */,
 				C398625D23AD6E90007E6793 /* UIFont+FLEX.h in Headers */,
-				3A4C953A1B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.h in Headers */,
+				3A4C953A1B5B21410088C3F2 /* FLEXNetworkSettingsController.h in Headers */,
 				C398626C23AD71C1007E6793 /* FLEXRuntimeController.h in Headers */,
 				779B1ED01C0C4D7C001F5E49 /* FLEXMultiColumnTableView.h in Headers */,
 				3A4C94D31B5B21410088C3F2 /* FLEXObjectExplorerFactory.h in Headers */,
@@ -1781,7 +1781,7 @@
 				3A4C94F41B5B21410088C3F2 /* FLEXArgumentInputFontView.m in Sources */,
 				C3694DBB23EA1096006625D7 /* FLEXTabsViewController.m in Sources */,
 				C3A9424623C641C1006871A3 /* SceneKit+Snapshot.m in Sources */,
-				3A4C953B1B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.m in Sources */,
+				3A4C953B1B5B21410088C3F2 /* FLEXNetworkSettingsController.m in Sources */,
 				3A4C94FC1B5B21410088C3F2 /* FLEXArgumentInputStringView.m in Sources */,
 				3A4C94F81B5B21410088C3F2 /* FLEXArgumentInputNotSupportedView.m in Sources */,
 				3A4C95351B5B21410088C3F2 /* FLEXSystemLogViewController.m in Sources */,