Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.

Commit 811f7f0

Browse files
authored
Update
1 parent 6e3fb96 commit 811f7f0

File tree

1 file changed

+101
-25
lines changed

1 file changed

+101
-25
lines changed

Sources/prostore/install/installApp.swift

Lines changed: 101 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ final class LocalStaticHTTPServer {
130130
private var rootDirectory: URL?
131131
private let queue = DispatchQueue(label: "LocalStaticHTTPServer.queue")
132132
private var serverStarted = false
133+
private var activeConnections: Set<NWConnection> = []
134+
private let connectionsLock = NSLock()
133135

134136
// Start HTTP (or HTTPS if tlsIdentity is provided) on given port. Serves static files from rootDir.
135137
func start(host: NWEndpoint.Host = .ipv4(IPv4Address("127.0.0.1")!),
@@ -140,6 +142,11 @@ final class LocalStaticHTTPServer {
140142
InstallLogger.shared.log("Starting HTTP server with port: \(port), tlsIdentity: \(tlsIdentity != nil ? "present" : "nil")")
141143
self.rootDirectory = rootDir
142144
self.serverStarted = false
145+
146+
// Clear any existing connections
147+
connectionsLock.lock()
148+
activeConnections.removeAll()
149+
connectionsLock.unlock()
143150

144151
// Create TCP params and attach TLS options if identity provided
145152
let tcpOptions = NWProtocolTCP.Options()
@@ -186,7 +193,6 @@ final class LocalStaticHTTPServer {
186193
listener.newConnectionHandler = { [weak self] connection in
187194
guard let self = self else { return }
188195
InstallLogger.shared.logDebug("New connection received")
189-
connection.start(queue: self.queue)
190196
self.handleConnection(connection)
191197
}
192198

@@ -251,13 +257,25 @@ final class LocalStaticHTTPServer {
251257

252258
func stop() {
253259
InstallLogger.shared.log("Stopping HTTP server")
260+
261+
connectionsLock.lock()
262+
for connection in activeConnections {
263+
connection.cancel()
264+
}
265+
activeConnections.removeAll()
266+
connectionsLock.unlock()
267+
254268
listener?.cancel()
255269
listener = nil
256270
serverStarted = false
257271
}
258272

259273
// Very minimal GET-only static file handler.
260274
private func handleConnection(_ connection: NWConnection) {
275+
connectionsLock.lock()
276+
activeConnections.insert(connection)
277+
connectionsLock.unlock()
278+
261279
var received = Data()
262280

263281
func receiveMore() {
@@ -266,7 +284,6 @@ final class LocalStaticHTTPServer {
266284
received.append(data)
267285
// If header end reached
268286
if let range = received.range(of: Data("\r\n\r\n".utf8)) {
269-
// fixed: use half-open Range 0..<range.upperBound
270287
self.processHTTPRequest(connection: connection, headerData: received.subdata(in: 0..<range.upperBound))
271288
return
272289
}
@@ -280,14 +297,14 @@ final class LocalStaticHTTPServer {
280297
}
281298
}
282299

300+
connection.start(queue: queue)
283301
receiveMore()
284302
}
285303

286304
private func processHTTPRequest(connection: NWConnection, headerData: Data) {
287-
defer { connection.cancel() }
288-
289305
guard let header = String(data: headerData, encoding: .utf8) else {
290306
InstallLogger.shared.logDebug("Failed to decode HTTP header")
307+
self.removeConnection(connection)
291308
return
292309
}
293310

@@ -298,9 +315,15 @@ final class LocalStaticHTTPServer {
298315
}
299316

300317
// parse the request line
301-
guard let requestLine = lines.first else { return }
318+
guard let requestLine = lines.first else {
319+
self.removeConnection(connection)
320+
return
321+
}
302322
let comps = requestLine.components(separatedBy: " ")
303-
guard comps.count >= 2 else { return }
323+
guard comps.count >= 2 else {
324+
self.removeConnection(connection)
325+
return
326+
}
304327
let method = comps[0]
305328
var path = comps[1]
306329
// strip query
@@ -350,13 +373,26 @@ final class LocalStaticHTTPServer {
350373
connection.send(content: headerDataToSend, completion: .contentProcessed({ _ in
351374
connection.send(content: data, completion: .contentProcessed({ _ in
352375
InstallLogger.shared.logDebug("File sent successfully: \(path)")
376+
// Don't cancel immediately, let the client close
377+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
378+
self.removeConnection(connection)
379+
}
353380
}))
354381
}))
355382
} catch {
356383
InstallLogger.shared.logError("Read error for file \(path): \(error.localizedDescription)")
357384
sendSimpleResponse(connection: connection, status: 500, text: "Read error: \(error.localizedDescription)")
358385
}
359386
}
387+
388+
private func removeConnection(_ connection: NWConnection) {
389+
connectionsLock.lock()
390+
if activeConnections.contains(connection) {
391+
connection.cancel()
392+
activeConnections.remove(connection)
393+
}
394+
connectionsLock.unlock()
395+
}
360396

361397
private func sendSimpleResponse(connection: NWConnection, status: Int, text: String) {
362398
let body = text + "\n"
@@ -368,6 +404,9 @@ final class LocalStaticHTTPServer {
368404
body ).utf8)
369405
connection.send(content: combined, completion: .contentProcessed({ _ in
370406
InstallLogger.shared.logDebug("Sent response: \(status) \(self.httpStatusText(status))")
407+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
408+
self.removeConnection(connection)
409+
}
371410
}))
372411
}
373412

@@ -639,45 +678,78 @@ public func installApp(from ipaURL: URL) throws {
639678
var tlsIdentity: sec_identity_t? = nil
640679
let p12URL = sslDir.appendingPathComponent("localhost.p12")
641680

681+
// Function to convert OSStatus to readable string
682+
func securityErrorToString(_ status: OSStatus) -> String {
683+
if let message = SecCopyErrorMessageString(status, nil) {
684+
return message as String
685+
}
686+
return "Unknown error: \(status)"
687+
}
688+
642689
if fm.fileExists(atPath: p12URL.path) {
643690
InstallLogger.shared.log("Found PKCS12 file at \(p12URL.path)")
644-
if let pData = try? Data(contentsOf: p12URL) {
691+
do {
692+
let pData = try Data(contentsOf: p12URL)
645693
InstallLogger.shared.log("PKCS12 file size: \(pData.count) bytes")
646-
// PKCS#12 has no password; pass empty string
694+
695+
// Try with empty password first
647696
let options: CFDictionary = [kSecImportExportPassphrase as String: ""] as CFDictionary
648697
var items: CFArray? = nil
649698
let status = SecPKCS12Import(pData as CFData, options, &items)
650-
InstallLogger.shared.log("SecPKCS12Import status: \(status)")
651-
699+
InstallLogger.shared.log("SecPKCS12Import status: \(status) - \(securityErrorToString(status))")
700+
652701
if status == errSecSuccess,
653702
let arr = items as? [[String: Any]],
654703
let first = arr.first {
655704
InstallLogger.shared.log("PKCS12 import successful, items count: \(arr.count)")
656705

657706
// The import dictionary values are Any; safely cast to SecIdentity
658707
if let identityAny = first[kSecImportItemIdentity as String] {
659-
do {
660-
let identityRef = identityAny as! SecIdentity
661-
InstallLogger.shared.log("SecIdentity cast successful")
662-
// Convert to sec_identity_t for sec_protocol_options_set_local_identity()
663-
if let secId = sec_identity_create(identityRef) {
664-
tlsIdentity = secId
665-
InstallLogger.shared.logSuccess("TLS identity created successfully")
666-
} else {
667-
InstallLogger.shared.logWarning("sec_identity_create failed; falling back to HTTP")
668-
}
669-
} catch {
670-
InstallLogger.shared.logError("Failed to cast to SecIdentity: \(error)")
708+
let identityRef = identityAny as! SecIdentity
709+
InstallLogger.shared.log("SecIdentity cast successful")
710+
// Convert to sec_identity_t for sec_protocol_options_set_local_identity()
711+
if let secId = sec_identity_create(identityRef) {
712+
tlsIdentity = secId
713+
InstallLogger.shared.logSuccess("TLS identity created successfully")
714+
} else {
715+
InstallLogger.shared.logWarning("sec_identity_create failed; falling back to HTTP")
671716
}
672717
} else {
673718
// No identity entry in the import result
674719
InstallLogger.shared.logWarning("PKCS#12 import produced no SecIdentity. Will use HTTP only.")
675720
}
676721
} else {
677-
InstallLogger.shared.logWarning("PKCS12 import failed (status \(status)). Will use HTTP only.")
722+
// Try with common passwords if empty password fails
723+
InstallLogger.shared.log("Trying common passwords for PKCS12 file...")
724+
let commonPasswords = ["password", "123456", "admin", "localhost", "ssl", "cert", "prostore"]
725+
var foundPassword = false
726+
727+
for password in commonPasswords {
728+
let optionsWithPassword: CFDictionary = [kSecImportExportPassphrase as String: password] as CFDictionary
729+
var passwordItems: CFArray? = nil
730+
let passwordStatus = SecPKCS12Import(pData as CFData, optionsWithPassword, &passwordItems)
731+
732+
if passwordStatus == errSecSuccess,
733+
let arr = passwordItems as? [[String: Any]],
734+
let first = arr.first,
735+
let identityAny = first[kSecImportItemIdentity as String] {
736+
737+
let identityRef = identityAny as! SecIdentity
738+
if let secId = sec_identity_create(identityRef) {
739+
tlsIdentity = secId
740+
InstallLogger.shared.logSuccess("TLS identity created successfully with password: '\(password)'")
741+
foundPassword = true
742+
break
743+
}
744+
}
745+
}
746+
747+
if !foundPassword {
748+
InstallLogger.shared.logWarning("PKCS12 import failed even with common passwords. Will use HTTP only.")
749+
}
678750
}
679-
} else {
680-
InstallLogger.shared.logWarning("Failed to read PKCS#12 file at \(p12URL.path); using HTTP only")
751+
} catch {
752+
InstallLogger.shared.logError("Failed to read PKCS#12 file: \(error)")
681753
}
682754
} else {
683755
InstallLogger.shared.log("No PKCS#12 found at \(p12URL.path); using HTTP only")
@@ -691,6 +763,10 @@ public func installApp(from ipaURL: URL) throws {
691763
do {
692764
startedPort = try LocalStaticHTTPServer.shared.start(port: chosenPort, rootDir: workDir, tlsIdentity: tlsIdentity)
693765
InstallLogger.shared.logSuccess("Server started on port \(startedPort)")
766+
767+
// Give the server a moment to stabilize
768+
InstallLogger.shared.log("Waiting for server to stabilize...")
769+
Thread.sleep(forTimeInterval: 0.5)
694770
} catch {
695771
InstallLogger.shared.logError("Failed to start local server: \(error)")
696772
throw InstallAppError.serverStartFailed("Failed to start local server: \(error)")

0 commit comments

Comments
 (0)