mirror of
https://github.com/immich-app/immich.git
synced 2026-03-22 11:09:21 +03:00
fix(mobile): use shared auth for background_downloader (#26911)
shared client for background_downloader on ios
This commit is contained in:
@@ -27,7 +27,11 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.net.Authenticator
|
||||||
|
import java.net.CookieHandler
|
||||||
|
import java.net.PasswordAuthentication
|
||||||
import java.net.Socket
|
import java.net.Socket
|
||||||
|
import java.net.URI
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.Principal
|
import java.security.Principal
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
@@ -104,6 +108,25 @@ object HttpClientManager {
|
|||||||
keyChainAlias = prefs.getString(PREFS_CERT_ALIAS, null)
|
keyChainAlias = prefs.getString(PREFS_CERT_ALIAS, null)
|
||||||
|
|
||||||
cookieJar.init(prefs)
|
cookieJar.init(prefs)
|
||||||
|
System.setProperty("http.agent", USER_AGENT)
|
||||||
|
Authenticator.setDefault(object : Authenticator() {
|
||||||
|
override fun getPasswordAuthentication(): PasswordAuthentication? {
|
||||||
|
val url = requestingURL ?: return null
|
||||||
|
if (url.userInfo.isNullOrEmpty()) return null
|
||||||
|
val parts = url.userInfo.split(":", limit = 2)
|
||||||
|
return PasswordAuthentication(parts[0], parts.getOrElse(1) { "" }.toCharArray())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
CookieHandler.setDefault(object : CookieHandler() {
|
||||||
|
override fun get(uri: URI, requestHeaders: Map<String, List<String>>): Map<String, List<String>> {
|
||||||
|
val httpUrl = uri.toString().toHttpUrlOrNull() ?: return emptyMap()
|
||||||
|
val cookies = cookieJar.loadForRequest(httpUrl)
|
||||||
|
if (cookies.isEmpty()) return emptyMap()
|
||||||
|
return mapOf("Cookie" to listOf(cookies.joinToString("; ") { "${it.name}=${it.value}" }))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun put(uri: URI, responseHeaders: Map<String, List<String>>) {}
|
||||||
|
})
|
||||||
|
|
||||||
val savedHeaders = prefs.getString(PREFS_HEADERS, null)
|
val savedHeaders = prefs.getString(PREFS_HEADERS, null)
|
||||||
if (savedHeaders != null) {
|
if (savedHeaders != null) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import UIKit
|
|||||||
}
|
}
|
||||||
|
|
||||||
SwiftNativeVideoPlayerPlugin.cookieStorage = URLSessionManager.cookieStorage
|
SwiftNativeVideoPlayerPlugin.cookieStorage = URLSessionManager.cookieStorage
|
||||||
|
URLSessionManager.patchBackgroundDownloader()
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
|
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
|
||||||
AppDelegate.registerPlugins(with: controller.engine, controller: controller)
|
AppDelegate.registerPlugins(with: controller.engine, controller: controller)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class URLSessionManager: NSObject {
|
|||||||
diskCapacity: 1024 * 1024 * 1024,
|
diskCapacity: 1024 * 1024 * 1024,
|
||||||
directory: cacheDir
|
directory: cacheDir
|
||||||
)
|
)
|
||||||
private static let userAgent: String = {
|
static let userAgent: String = {
|
||||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown"
|
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown"
|
||||||
return "Immich_iOS_\(version)"
|
return "Immich_iOS_\(version)"
|
||||||
}()
|
}()
|
||||||
@@ -158,6 +158,49 @@ class URLSessionManager: NSObject {
|
|||||||
|
|
||||||
return URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
|
return URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Patches background_downloader's URLSession to use shared auth configuration.
|
||||||
|
/// Must be called before background_downloader creates its session (i.e. early in app startup).
|
||||||
|
static func patchBackgroundDownloader() {
|
||||||
|
// Swizzle URLSessionConfiguration.background(withIdentifier:) to inject shared config
|
||||||
|
let originalSel = NSSelectorFromString("backgroundSessionConfigurationWithIdentifier:")
|
||||||
|
let swizzledSel = #selector(URLSessionConfiguration.immich_background(withIdentifier:))
|
||||||
|
if let original = class_getClassMethod(URLSessionConfiguration.self, originalSel),
|
||||||
|
let swizzled = class_getClassMethod(URLSessionConfiguration.self, swizzledSel) {
|
||||||
|
method_exchangeImplementations(original, swizzled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add auth challenge handling to background_downloader's UrlSessionDelegate
|
||||||
|
guard let targetClass = NSClassFromString("background_downloader.UrlSessionDelegate") else { return }
|
||||||
|
|
||||||
|
let sessionBlock: @convention(block) (AnyObject, URLSession, URLAuthenticationChallenge,
|
||||||
|
@escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void
|
||||||
|
= { _, session, challenge, completion in
|
||||||
|
URLSessionManager.shared.delegate.handleChallenge(session, challenge, completion)
|
||||||
|
}
|
||||||
|
class_replaceMethod(targetClass,
|
||||||
|
NSSelectorFromString("URLSession:didReceiveChallenge:completionHandler:"),
|
||||||
|
imp_implementationWithBlock(sessionBlock), "v@:@@@?")
|
||||||
|
|
||||||
|
let taskBlock: @convention(block) (AnyObject, URLSession, URLSessionTask, URLAuthenticationChallenge,
|
||||||
|
@escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void
|
||||||
|
= { _, session, task, challenge, completion in
|
||||||
|
URLSessionManager.shared.delegate.handleChallenge(session, challenge, completion, task: task)
|
||||||
|
}
|
||||||
|
class_replaceMethod(targetClass,
|
||||||
|
NSSelectorFromString("URLSession:task:didReceiveChallenge:completionHandler:"),
|
||||||
|
imp_implementationWithBlock(taskBlock), "v@:@@@@?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension URLSessionConfiguration {
|
||||||
|
@objc dynamic class func immich_background(withIdentifier id: String) -> URLSessionConfiguration {
|
||||||
|
// After swizzle, this calls the original implementation
|
||||||
|
let config = immich_background(withIdentifier: id)
|
||||||
|
config.httpCookieStorage = URLSessionManager.cookieStorage
|
||||||
|
config.httpAdditionalHeaders = ["User-Agent": URLSessionManager.userAgent]
|
||||||
|
return config
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class URLSessionManagerDelegate: NSObject, URLSessionTaskDelegate, URLSessionWebSocketDelegate {
|
class URLSessionManagerDelegate: NSObject, URLSessionTaskDelegate, URLSessionWebSocketDelegate {
|
||||||
|
|||||||
Reference in New Issue
Block a user