URLHandler.swift 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /*****************************************************************************
  2. * URLHandler.swift
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2018 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Carola Nitz <caro # videolan.org>
  9. *
  10. * Refer to the COPYING file of the official project for license.
  11. *****************************************************************************/
  12. import Foundation
  13. @objc public protocol VLCURLHandler {
  14. func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool
  15. func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool
  16. }
  17. @objc class URLHandlers: NSObject {
  18. @objc static let googleURLHandler = GoogleURLHandler()
  19. @objc static let handlers =
  20. [
  21. googleURLHandler,
  22. DropBoxURLHandler(),
  23. FileURLHandler(),
  24. XCallbackURLHandler(),
  25. VLCCallbackURLHandler(),
  26. ElseCallbackURLHandler()
  27. ]
  28. }
  29. class DropBoxURLHandler: NSObject, VLCURLHandler {
  30. @objc func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
  31. let components = url.pathComponents
  32. let methodName = components.count > 1 ? components[1] : nil
  33. if methodName == "cancel" {
  34. return false
  35. }
  36. return url.scheme == "db-a60fc6qj9zdg7bw"
  37. }
  38. @objc func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
  39. let authResult = DBClientsManager.handleRedirectURL(url)
  40. if let authResult = authResult, authResult.isSuccess() == true {
  41. //TODO:update Dropboxcontrollers
  42. return true
  43. }
  44. return false
  45. }
  46. }
  47. class GoogleURLHandler: NSObject, VLCURLHandler {
  48. @objc var currentGoogleAuthorizationFlow: OIDExternalUserAgentSession?
  49. @objc func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
  50. return url.scheme == "com.googleusercontent.apps.CLIENT"
  51. }
  52. @objc func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
  53. if currentGoogleAuthorizationFlow?.resumeExternalUserAgentFlow(with: url) == true {
  54. currentGoogleAuthorizationFlow = nil
  55. return true
  56. }
  57. return false
  58. }
  59. }
  60. class FileURLHandler: NSObject, VLCURLHandler {
  61. @objc func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
  62. return url.isFileURL
  63. }
  64. @objc func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
  65. let subclass = DocumentClass(fileURL: url)
  66. subclass.open { _ in
  67. self.play(url: url) { _ in
  68. subclass.close(completionHandler: nil)
  69. }
  70. }
  71. return true
  72. }
  73. }
  74. class XCallbackURLHandler: NSObject, VLCURLHandler {
  75. @objc func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
  76. return url.scheme == "vlc-x-callback" || url.scheme == "x-callback-url"
  77. }
  78. @objc func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
  79. let action = url.path.replacingOccurrences(of: "/", with: "")
  80. var movieURL: URL?
  81. var subURL: URL?
  82. var successCallback: URL?
  83. var errorCallback: URL?
  84. var fileName: String?
  85. guard let query = url.query else {
  86. assertionFailure("no query")
  87. return false
  88. }
  89. for entry in query.components(separatedBy: "&") {
  90. let components = entry.components(separatedBy: "=")
  91. if components.count < 2 {
  92. continue
  93. }
  94. if let key = components.first, let value = components[1].removingPercentEncoding {
  95. if key == "url"{
  96. movieURL = URL(string: value)
  97. } else if key == "sub" {
  98. subURL = URL(string: value)
  99. } else if key == "filename" {
  100. fileName = value
  101. } else if key == "x-success" {
  102. successCallback = URL(string: value)
  103. } else if key == "x-error" {
  104. errorCallback = URL(string: value)
  105. }
  106. } else {
  107. assertionFailure("no key or app value")
  108. }
  109. }
  110. if action == "stream", let movieURL = movieURL {
  111. play(url: movieURL, sub: subURL) { success in
  112. guard let callback = success ? successCallback : errorCallback else {
  113. assertionFailure("no CallbackURL")
  114. return
  115. }
  116. if #available(iOS 10, *) {
  117. UIApplication.shared.open(callback, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
  118. } else {
  119. UIApplication.shared.openURL(callback)
  120. }
  121. }
  122. return true
  123. } else if action == "download", let movieURL = movieURL {
  124. downloadMovie(from:movieURL, fileNameOfMedia:fileName)
  125. return true
  126. }
  127. return false
  128. }
  129. }
  130. public class VLCCallbackURLHandler: NSObject, VLCURLHandler {
  131. @objc public func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
  132. return url.scheme == "vlc"
  133. }
  134. // Safari fixes URLs like "vlc://http://example.org" to "vlc://http//example.org"
  135. public func transformVLCURL(_ url: URL) -> URL {
  136. var parsedString = url.absoluteString.replacingOccurrences(of: "vlc://", with: "")
  137. if let location = parsedString.range(of: "//"), parsedString[parsedString.index(location.lowerBound, offsetBy: -1)] != ":" {
  138. parsedString = "\(parsedString[parsedString.startIndex..<location.lowerBound])://\(parsedString[location.upperBound...])"
  139. } else if !parsedString.hasPrefix("http://") && !parsedString.hasPrefix("https://") && !parsedString.hasPrefix("ftp://") {
  140. parsedString = "http://\(parsedString)"
  141. }
  142. return URL(string: parsedString)!
  143. }
  144. public func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
  145. let transformedURL = transformVLCURL(url)
  146. let scheme = transformedURL.scheme
  147. if scheme == "http" || scheme == "https" || scheme == "ftp" {
  148. let alert = UIAlertController(title: NSLocalizedString("OPEN_STREAM_OR_DOWNLOAD", comment:""), message: url.absoluteString, preferredStyle: .alert)
  149. let downloadAction = UIAlertAction(title: NSLocalizedString("BUTTON_DOWNLOAD", comment:""), style: .default) { _ in
  150. self.downloadMovie(from:transformedURL, fileNameOfMedia:nil)
  151. }
  152. alert.addAction(downloadAction)
  153. let playAction = UIAlertAction(title: NSLocalizedString("PLAY_BUTTON", comment:""), style: .default) { _ in
  154. self.play(url: transformedURL, completion: nil)
  155. }
  156. alert.addAction(playAction)
  157. var rootViewController = UIApplication.shared.keyWindow?.rootViewController
  158. if let tabBarController = UIApplication.shared.keyWindow?.rootViewController
  159. as? UITabBarController {
  160. rootViewController = tabBarController.selectedViewController
  161. }
  162. rootViewController?.present(alert, animated: true, completion: nil)
  163. } else {
  164. self.play(url: transformedURL, completion: nil)
  165. }
  166. return true
  167. }
  168. }
  169. class ElseCallbackURLHandler: NSObject, VLCURLHandler {
  170. @objc func canHandleOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
  171. guard let scheme = url.scheme else {
  172. return false
  173. }
  174. return scheme.range(of: kSupportedProtocolSchemes,
  175. options: [.regularExpression, .caseInsensitive], range: nil, locale: nil) != nil
  176. }
  177. func performOpen(url: URL, options: [UIApplication.OpenURLOptionsKey: AnyObject]) -> Bool {
  178. self.play(url: url, completion: nil)
  179. return true
  180. }
  181. }
  182. extension VLCURLHandler {
  183. // TODO: This code should probably not live here
  184. func play(url: URL, sub: URL? = nil, completion: ((Bool) -> Void)?) {
  185. let vpc = PlaybackService.sharedInstance()
  186. vpc.fullscreenSessionRequested = true
  187. if let mediaList = VLCMediaList(array: [VLCMedia(url: url)]) {
  188. vpc.playMediaList(mediaList, firstIndex: 0, subtitlesFilePath: sub?.absoluteString, completion: completion)
  189. }
  190. }
  191. func downloadMovie(from url: URL, fileNameOfMedia fileName: String?) {
  192. VLCDownloadViewController.sharedInstance().addURL(toDownloadList: url, fileNameOfMedia: fileName)
  193. }
  194. }
  195. // Helper function inserted by Swift 4.2 migrator.
  196. fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
  197. return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
  198. }