| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- //
- // ViewController.swift
- // TunneledWebView
- //
- /*
- Licensed under Creative Commons Zero (CC0).
- https://creativecommons.org/publicdomain/zero/1.0/
- */
- import UIKit
- import PsiphonTunnel
- class ViewController: UIViewController {
-
- var webView: UIWebView!
-
- // The instance of PsiphonTunnel we'll use for connecting.
- var psiphonTunnel: PsiphonTunnel?
-
- // These are the ports that we can proxy through.
- var socksProxyPort = -1
- var httpProxyPort = -1
-
- override func loadView() {
- // Make our whole view the webview.
- webView = UIWebView()
- view = webView
-
- self.psiphonTunnel = PsiphonTunnel.newPsiphonTunnel(self)
- }
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- // Start up the tunnel and begin connecting.
- // This could be started elsewhere or earlier.
- NSLog("Starting tunnel")
- let embeddedServerEntries = ""
- guard let success = self.psiphonTunnel?.start(embeddedServerEntries), success else {
- NSLog("psiphonTunnel.start returned false")
- return
- }
- }
-
- override func didReceiveMemoryWarning() {
- super.didReceiveMemoryWarning()
- // Dispose of any resources that can be recreated.
- }
-
- func appendToView(_ text: String) {
- let escapedText = text.replacingOccurrences(of: "\n", with: "\\n")
- .replacingOccurrences(of: "\r", with: "")
- self.webView.stringByEvaluatingJavaScript(from: String.init(format: "document.body.innerHTML+='<br><pre>%@</pre><br>'", arguments: [escapedText]))
- }
-
- /// Request URL using URLSession configured to use the current proxy.
- /// * parameters:
- /// - url: The URL to request.
- /// - completion: A callback function that will received the string obtained
- /// from the request, or nil if there's an error.
- /// * returns: The string obtained from the request, or nil if there's an error.
- func makeRequestViaUrlSessionProxy(_ url: String, completion: @escaping (_ result: String?) -> ()) {
- assert(self.httpProxyPort > 0)
-
- let request = URLRequest(url: URL(string: url)!)
-
- let config = URLSessionConfiguration.ephemeral
- config.requestCachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
- config.connectionProxyDictionary = [AnyHashable: Any]()
-
- // Enable and set the SOCKS proxy values.
- config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxy as String] = 1
- config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxyHost as String] = "127.0.0.1"
- config.connectionProxyDictionary?[kCFStreamPropertySOCKSProxyPort as String] = self.socksProxyPort
-
- // Alternatively, the HTTP proxy can be used. Below are the settings for that.
- // The HTTPS key constants are mismatched and Xcode gives deprecation warnings, but they seem to be necessary to proxy HTTPS requests. This is probably a bug on Apple's side; see: https://forums.developer.apple.com/thread/19356#131446
- // config.connectionProxyDictionary?[kCFNetworkProxiesHTTPEnable as String] = 1
- // config.connectionProxyDictionary?[kCFNetworkProxiesHTTPProxy as String] = "127.0.0.1"
- // config.connectionProxyDictionary?[kCFNetworkProxiesHTTPPort as String] = self.httpProxyPort
- // config.connectionProxyDictionary?[kCFStreamPropertyHTTPSProxyHost as String] = "127.0.0.1"
- // config.connectionProxyDictionary?[kCFStreamPropertyHTTPSProxyPort as String] = self.httpProxyPort
-
- let session = URLSession.init(configuration: config, delegate: nil, delegateQueue: OperationQueue.current)
-
- // Create the URLSession task that will make the request via the tunnel proxy.
- let task = session.dataTask(with: request) {
- (data: Data?, response: URLResponse?, error: Error?) in
- if error != nil {
- NSLog("Client-side error in request to \(url): \(error)")
- // Invoke the callback indicating error.
- completion(nil)
- return
- }
-
- if data == nil {
- NSLog("Data from request to \(url) is nil")
- // Invoke the callback indicating error.
- completion(nil)
- return
- }
-
- let httpResponse = response as? HTTPURLResponse
- if httpResponse?.statusCode != 200 {
- NSLog("Server-side error in request to \(url): \(httpResponse)")
- // Invoke the callback indicating error.
- completion(nil)
- return
- }
-
- let encodingName = response?.textEncodingName != nil ? response?.textEncodingName : "utf-8"
- let encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString!))
-
- let stringData = String(data: data!, encoding: String.Encoding(rawValue: UInt(encoding)))
-
- // Make sure the session is cleaned up.
- session.invalidateAndCancel()
-
- // Invoke the callback with the result.
- completion(stringData)
- }
-
- // Start the request task.
- task.resume()
- }
- /// Request URL using Psiphon's "URL proxy" mode.
- /// For details, see the comment near the top of:
- /// https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/master/psiphon/httpProxy.go
- /// * parameters:
- /// - url: The URL to request.
- /// - completion: A callback function that will received the string obtained
- /// from the request, or nil if there's an error.
- /// * returns: The string obtained from the request, or nil if there's an error.
- func makeRequestViaUrlProxy(_ url: String, completion: @escaping (_ result: String?) -> ()) {
- assert(self.httpProxyPort > 0)
-
- // The target URL must be encoded so as to be valid within a query parameter.
- // See this SO answer for why we're using this CharacterSet (and not using: https://stackoverflow.com/a/24888789
- let queryParamCharsAllowed = CharacterSet.init(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
- let encodedTargetURL = url.addingPercentEncoding(withAllowedCharacters: queryParamCharsAllowed)
-
- let proxiedURL = "http://127.0.0.1:\(self.httpProxyPort)/tunneled/\(encodedTargetURL!)"
-
- let task = URLSession.shared.dataTask(with: URL(string: proxiedURL)!) {
- (data: Data?, response: URLResponse?, error: Error?) in
- if error != nil {
- NSLog("Client-side error in request to \(url): \(error)")
- // Invoke the callback indicating error.
- completion(nil)
- return
- }
-
- if data == nil {
- NSLog("Data from request to \(url) is nil")
- // Invoke the callback indicating error.
- completion(nil)
- return
- }
-
- let httpResponse = response as? HTTPURLResponse
- if httpResponse?.statusCode != 200 {
- NSLog("Server-side error in request to \(url): \(httpResponse)")
- // Invoke the callback indicating error.
- completion(nil)
- return
- }
-
- let encodingName = response?.textEncodingName != nil ? response?.textEncodingName : "utf-8"
- let encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString!))
-
- let stringData = String(data: data!, encoding: String.Encoding(rawValue: UInt(encoding)))
-
- // Invoke the callback with the result.
- completion(stringData)
- }
-
- // Start the request task.
- task.resume()
- }
- }
- // MARK: TunneledAppDelegate implementation
- // See the protocol definition for details about the methods.
- // Note that we're excluding all the optional methods that we aren't using,
- // however your needs may be different.
- extension ViewController: TunneledAppDelegate {
- func getPsiphonConfig() -> String? {
- // In this example, we're going to retrieve our Psiphon config from a file in the app bundle.
- // Alternatively, it could be a string literal in the code, or whatever makes sense.
-
- guard let psiphonConfigUrl = Bundle.main.url(forResource: "psiphon-config", withExtension: "json") else {
- NSLog("Error getting Psiphon config resource file URL!")
- return nil
- }
-
- do {
- return try String.init(contentsOf: psiphonConfigUrl)
- } catch {
- NSLog("Error getting Psiphon config resource file URL!")
- return nil
- }
- }
-
- func onDiagnosticMessage(_ message: String) {
- NSLog("onDiagnosticMessage: %@", message)
- }
-
- func onConnected() {
- NSLog("onConnected")
-
- // After we're connected, make tunneled requests and populate the webview.
-
- DispatchQueue.global(qos: .default).async {
- // First we'll make a "what is my IP" request via makeRequestViaUrlSessionProxy().
- let url = "https://freegeoip.net/json/"
- self.makeRequestViaUrlSessionProxy(url) {
- (_ result: String?) in
-
- if result == nil {
- NSLog("Failed to get \(url)")
- return
- }
- // Do a little pretty-printing.
- let prettyResult = result?.replacingOccurrences(of: ",", with: ",\n ")
- .replacingOccurrences(of: "{", with: "{\n ")
- .replacingOccurrences(of: "}", with: "\n}")
-
- DispatchQueue.main.sync {
- // Load the result into the view.
- self.appendToView("Result from \(url):\n\(prettyResult!)")
- }
-
- // Then we'll make a different "what is my IP" request via makeRequestViaUrlProxy().
- DispatchQueue.global(qos: .default).async {
- let url = "http://ipinfo.io/json"
- self.makeRequestViaUrlProxy(url) {
- (_ result: String?) in
-
- if result == nil {
- NSLog("Failed to get \(url)")
- return
- }
-
- DispatchQueue.main.sync {
- // Load the result into the view.
- self.appendToView("Result from \(url):\n\(result!)")
- }
-
- // We're done with the Psiphon tunnel, so stop it.
- // In a real app, we would keep this alive for as long as we need it.
- self.psiphonTunnel?.stop()
- }
- }
-
- }
- }
- }
-
- func onListeningSocksProxyPort(_ port: Int) {
- NSLog("onListeningSocksProxyPort: %d", port)
- // Record the port being used so that we can proxy through it later.
- DispatchQueue.main.async {
- self.socksProxyPort = port
- }
- }
-
- func onListeningHttpProxyPort(_ port: Int) {
- NSLog("onListeningHttpProxyPort: %d", port)
- // Record the port being used so that we can proxy through it later.
- DispatchQueue.main.async {
- self.httpProxyPort = port
- }
- }
- }
|