SimpleTunnel/ContentFilterController.swift
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
This file contains the ContentFilterController class, which controls a view used to start and stop a content filter, and display current filtering rules. |
*/ |
import UIKit |
import NetworkExtension |
import SimpleTunnelServices |
/// A view controller object for a view that displays Content Filter configuration and rules. |
class ContentFilterController: UITableViewController { |
// MARK: Properties |
/// A table view cell that contains the status of the filter. |
@IBOutlet weak var statusCell: SwitchCell! |
/// A table view cell that contains the |
@IBOutlet weak var rulesServerCell: TextFieldCell! |
/// A variable to pass as the context to addObserver() |
var rulesContext = 0 |
/// The current list of filtering rules |
var currentRules = [(String, String)]() |
// MARK: Initializers |
deinit { |
var context = self |
FilterUtilities.defaults?.removeObserver(self, forKeyPath: "rules", context:&context) |
} |
// MARK: UIViewController |
/// Handle the event where the view is loaded into memory. |
override func viewDidLoad() { |
FilterUtilities.defaults?.addObserver(self, forKeyPath: "rules", options: NSKeyValueObservingOptions.initial, context:&rulesContext) |
statusCell.valueChanged = { |
if self.statusCell.isOn && NEFilterManager.shared().providerConfiguration == nil { |
let newConfiguration = NEFilterProviderConfiguration() |
newConfiguration.username = "TestUser" |
newConfiguration.organization = "Acme Inc." |
newConfiguration.filterBrowsers = true |
newConfiguration.filterSockets = true |
newConfiguration.serverAddress = self.rulesServerCell.textField.text ?? "my.great.filter.server" |
NEFilterManager.shared().providerConfiguration = newConfiguration |
} |
NEFilterManager.shared().isEnabled = self.statusCell.isOn |
NEFilterManager.shared().saveToPreferences { error in |
if let saveError = error { |
simpleTunnelLog("Failed to save the filter configuration: \(saveError)") |
self.statusCell.isOn = false |
return |
} |
self.rulesServerCell.textField.text = NEFilterManager.shared().providerConfiguration?.serverAddress |
FilterUtilities.defaults?.setValue(NEFilterManager.shared().providerConfiguration?.serverAddress, forKey: "serverAddress") |
} |
} |
rulesServerCell.valueChanged = { |
guard let serverIPAddress = self.rulesServerCell.textField.text , !serverIPAddress.isEmpty else { return } |
NEFilterManager.shared().providerConfiguration?.serverAddress = serverIPAddress |
NEFilterManager.shared().saveToPreferences { error in |
if let saveError = error { |
simpleTunnelLog("Failed to save the filter configuration: \(saveError)") |
return |
} |
FilterUtilities.defaults?.setValue(serverIPAddress, forKey: "serverAddress") |
} |
} |
} |
/// Handle the event where the view is loaded into memory. |
override func viewWillAppear(_ animated: Bool) { |
super.viewWillAppear(animated) |
NEFilterManager.shared().loadFromPreferences { error in |
if let loadError = error { |
simpleTunnelLog("Failed to load the filter configuration: \(loadError)") |
self.statusCell.isOn = false |
return |
} |
self.statusCell.isOn = NEFilterManager.shared().isEnabled |
self.rulesServerCell.textField.text = NEFilterManager.shared().providerConfiguration?.serverAddress |
self.reloadRules() |
} |
} |
// MARK: NSObject |
/// Handle changes to the rules |
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { |
if context == &rulesContext && keyPath == "rules" { |
reloadRules() |
} else { |
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) |
} |
} |
// MARK: UITableViewDataSource |
/// Return the number of sections to display. |
override func numberOfSections(in tableView: UITableView) -> Int { |
return !currentRules.isEmpty ? 2 : 1 |
} |
/// Return the number of rows in a section. |
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { |
switch section { |
case 0: return 2 |
case 1: return currentRules.count |
default: return 0 |
} |
} |
/// Return the cell for given index path. |
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { |
if (indexPath as NSIndexPath).section == 0 { |
return (indexPath as NSIndexPath).row == 0 ? statusCell : rulesServerCell |
} |
else if let cell = tableView.dequeueReusableCell(withIdentifier: "rule-cell") { |
let (hostString, actionString) = currentRules[(indexPath as NSIndexPath).row] |
cell.textLabel?.text = hostString |
cell.detailTextLabel?.text = actionString |
return cell |
} |
else { |
return UITableViewCell() |
} |
} |
/// Return the title for a section in the table. |
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { |
return section == 1 ? "Current Rules" : nil |
} |
// MARK: Interface |
/// Re-load the current filerting rules from the defaults. |
func reloadRules() { |
currentRules = [(String, String)]() |
guard let rules = FilterUtilities.defaults?.object(forKey: "rules") as? [String : [String : AnyObject]] else { return } |
for (hostname, ruleInfo) in rules { |
guard let ruleActionNum = ruleInfo["kRule"] as? Int, |
let ruleAction = FilterRuleAction(rawValue: ruleActionNum) |
else { continue } |
currentRules.append((hostname as String, ruleAction.description)) |
} |
tableView.reloadData() |
} |
/// Download a new set of filtering rules from the server. |
@IBAction func fetchRulesButtonTouchUpInside(_ sender: UIButton) { |
FilterUtilities.fetchRulesFromServer(NEFilterManager.shared().providerConfiguration?.serverAddress) |
reloadRules() |
} |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-10-04