/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import AVFoundation import UIKit open class UserAgent { private static var defaults = UserDefaults(suiteName: AppInfo.sharedContainerIdentifier)! private static func clientUserAgent(prefix: String) -> String { return "\(prefix)/\(AppInfo.appVersion)b\(AppInfo.buildNumber) (\(DeviceInfo.deviceModel()); iPhone OS \(UIDevice.current.systemVersion)) (\(AppInfo.displayName))" } open static var syncUserAgent: String { return clientUserAgent(prefix: "Firefox-iOS-Sync") } open static var tokenServerClientUserAgent: String { return clientUserAgent(prefix: "Firefox-iOS-Token") } open static var fxaUserAgent: String { return clientUserAgent(prefix: "Firefox-iOS-FxA") } open static var defaultClientUserAgent: String { return clientUserAgent(prefix: "Firefox-iOS") } /** * Use this if you know that a value must have been computed before your * code runs, or you don't mind failure. */ open static func cachedUserAgent(checkiOSVersion: Bool = true, checkFirefoxVersion: Bool = true, checkFirefoxBuildNumber: Bool = true) -> String? { let currentiOSVersion = UIDevice.current.systemVersion let lastiOSVersion = defaults.string(forKey: "LastDeviceSystemVersionNumber") let currentFirefoxBuildNumber = AppInfo.buildNumber let currentFirefoxVersion = AppInfo.appVersion let lastFirefoxVersion = defaults.string(forKey: "LastFirefoxVersionNumber") let lastFirefoxBuildNumber = defaults.string(forKey: "LastFirefoxBuildNumber") if let firefoxUA = defaults.string(forKey: "UserAgent") { if (!checkiOSVersion || (lastiOSVersion == currentiOSVersion)) && (!checkFirefoxVersion || (lastFirefoxVersion == currentFirefoxVersion) && (!checkFirefoxBuildNumber || (lastFirefoxBuildNumber == currentFirefoxBuildNumber))) { return firefoxUA } } return nil } /** * This will typically return quickly, but can require creation of a UIWebView. * As a result, it must be called on the UI thread. */ open static func defaultUserAgent() -> String { assert(Thread.current.isMainThread, "This method must be called on the main thread.") if let firefoxUA = UserAgent.cachedUserAgent(checkiOSVersion: true) { return firefoxUA } let webView = UIWebView() let appVersion = AppInfo.appVersion let buildNumber = AppInfo.buildNumber let currentiOSVersion = UIDevice.current.systemVersion defaults.set(currentiOSVersion, forKey: "LastDeviceSystemVersionNumber") defaults.set(appVersion, forKey: "LastFirefoxVersionNumber") defaults.set(buildNumber, forKey: "LastFirefoxBuildNumber") let userAgent = webView.stringByEvaluatingJavaScript(from: "navigator.userAgent")! // Extract the WebKit version and use it as the Safari version. let webKitVersionRegex = try! NSRegularExpression(pattern: "AppleWebKit/([^ ]+) ", options: []) let match = webKitVersionRegex.firstMatch(in: userAgent, options: [], range: NSRange(location: 0, length: userAgent.characters.count)) if match == nil { print("Error: Unable to determine WebKit version in UA.") return userAgent // Fall back to Safari's. } let webKitVersion = (userAgent as NSString).substring(with: match!.rangeAt(1)) // Insert "FxiOS/" before the Mobile/ section. let mobileRange = (userAgent as NSString).range(of: "Mobile/") if mobileRange.location == NSNotFound { print("Error: Unable to find Mobile section in UA.") return userAgent // Fall back to Safari's. } let mutableUA = NSMutableString(string: userAgent) mutableUA.insert("FxiOS/\(appVersion)b\(AppInfo.buildNumber) ", at: mobileRange.location) let firefoxUA = "\(mutableUA) Safari/\(webKitVersion)" defaults.set(firefoxUA, forKey: "UserAgent") return firefoxUA } open static func desktopUserAgent() -> String { let userAgent = NSMutableString(string: defaultUserAgent()) // Spoof platform section let platformRegex = try! NSRegularExpression(pattern: "\\([^\\)]+\\)", options: []) guard let platformMatch = platformRegex.firstMatch(in: userAgent as String, options: [], range: NSRange(location: 0, length: userAgent.length)) else { print("Error: Unable to determine platform in UA.") return String(userAgent) } userAgent.replaceCharacters(in: platformMatch.range, with: "(Macintosh; Intel Mac OS X 10_11_1)") // Strip mobile section let mobileRegex = try! NSRegularExpression(pattern: " FxiOS/[^ ]+ Mobile/[^ ]+", options: []) guard let mobileMatch = mobileRegex.firstMatch(in: userAgent as String, options: [], range: NSRange(location: 0, length: userAgent.length)) else { print("Error: Unable to find Mobile section in UA.") return String(userAgent) } userAgent.replaceCharacters(in: mobileMatch.range, with: "") return String(userAgent) } }