@@ -31,13 +31,25 @@ class DataPackageImportManager: ObservableObject {
3131
3232 // Extract zip file
3333 await statusCallback ( . extracting)
34- try await extractZipFile ( from: url, to: tempDir)
34+ try extractZipFile ( from: url, to: tempDir)
3535
3636 // Find and process contents
3737 let contents = try findPackageContents ( in: tempDir)
3838
39- // Import certificates
4039 var importedItems = 0
40+
41+ // Parse preferences FIRST to get passwords for certificate import
42+ await statusCallback ( . configuring)
43+ for prefURL in contents. preferences {
44+ do {
45+ try await parsePreferences ( from: prefURL)
46+ importedItems += 1
47+ } catch {
48+ print ( " ⚠️ Failed to parse preferences: \( error. localizedDescription) " )
49+ }
50+ }
51+
52+ // Import certificates (using passwords extracted from preferences)
4153 for certURL in contents. certificates {
4254 do {
4355 try await importCertificate ( from: certURL)
@@ -47,8 +59,7 @@ class DataPackageImportManager: ObservableObject {
4759 }
4860 }
4961
50- // Parse and apply server configurations
51- await statusCallback ( . configuring)
62+ // Parse and apply additional server configurations
5263 for configURL in contents. serverConfigs {
5364 do {
5465 try await parseServerConfig ( from: configURL)
@@ -58,15 +69,6 @@ class DataPackageImportManager: ObservableObject {
5869 }
5970 }
6071
61- // Parse preferences
62- for prefURL in contents. preferences {
63- do {
64- try await parsePreferences ( from: prefURL)
65- } catch {
66- print ( " ⚠️ Failed to parse preferences: \( error. localizedDescription) " )
67- }
68- }
69-
7072 // Record import
7173 let record = ImportRecord (
7274 packageName: url. deletingPathExtension ( ) . lastPathComponent,
@@ -83,7 +85,7 @@ class DataPackageImportManager: ObservableObject {
8385
8486 // MARK: - Extract ZIP
8587
86- private func extractZipFile( from sourceURL: URL , to destinationURL: URL ) async throws {
88+ private func extractZipFile( from sourceURL: URL , to destinationURL: URL ) throws {
8789 // Read the ZIP data
8890 let zipData = try Data ( contentsOf: sourceURL)
8991
@@ -117,6 +119,9 @@ class DataPackageImportManager: ObservableObject {
117119 var serverConfigs : [ URL ] = [ ]
118120 var preferences : [ URL ] = [ ]
119121
122+ // First, extract any nested zip files (TAK data packages often have nested zips)
123+ try extractNestedZips ( in: directory)
124+
120125 let enumerator = fileManager. enumerator ( at: directory, includingPropertiesForKeys: [ . isRegularFileKey] )
121126
122127 while let fileURL = enumerator? . nextObject ( ) as? URL {
@@ -127,14 +132,14 @@ class DataPackageImportManager: ObservableObject {
127132 if ext == " p12 " || ext == " pfx " || ext == " pem " || ext == " crt " || ext == " cer " {
128133 certificates. append ( fileURL)
129134 }
130- // Server config files
131- else if ext == " xml " || ext == " json " || filename. contains ( " server " ) || filename. contains ( " connection " ) {
132- serverConfigs. append ( fileURL)
133- }
134- // Preference files
135- else if filename. contains ( " pref " ) || filename. contains ( " config " ) {
135+ // Preference files (check first - .pref files contain server config)
136+ else if ext == " pref " || filename. contains ( " preference " ) {
136137 preferences. append ( fileURL)
137138 }
139+ // Server config files (but not manifest.xml)
140+ else if ( ext == " xml " && !filename. contains ( " manifest " ) ) || ext == " json " || filename. contains ( " server " ) || filename. contains ( " connection " ) {
141+ serverConfigs. append ( fileURL)
142+ }
138143 }
139144
140145 return PackageContents (
@@ -144,6 +149,29 @@ class DataPackageImportManager: ObservableObject {
144149 )
145150 }
146151
152+ // MARK: - Extract Nested Zips
153+
154+ private func extractNestedZips( in directory: URL ) throws {
155+ let enumerator = fileManager. enumerator ( at: directory, includingPropertiesForKeys: [ . isRegularFileKey] )
156+
157+ while let fileURL = enumerator? . nextObject ( ) as? URL {
158+ if fileURL. pathExtension. lowercased ( ) == " zip " {
159+ let nestedDir = fileURL. deletingPathExtension ( )
160+ try fileManager. createDirectory ( at: nestedDir, withIntermediateDirectories: true )
161+
162+ do {
163+ try extractZipFile ( from: fileURL, to: nestedDir)
164+ print ( " 📦 Extracted nested zip: \( fileURL. lastPathComponent) " )
165+
166+ // Recursively extract any further nested zips
167+ try extractNestedZips ( in: nestedDir)
168+ } catch {
169+ print ( " ⚠️ Failed to extract nested zip: \( error. localizedDescription) " )
170+ }
171+ }
172+ }
173+ }
174+
147175 // MARK: - Import Certificate
148176
149177 private func importCertificate( from url: URL ) async throws {
@@ -154,8 +182,19 @@ class DataPackageImportManager: ObservableObject {
154182 let ext = url. pathExtension. lowercased ( )
155183
156184 if ext == " p12 " || ext == " pfx " {
157- // P12/PFX file - try common passwords or prompt user
158- let passwords = [ " atakatak " , " " ] // Common TAK server passwords
185+ // P12/PFX file - try passwords from preferences first, then common defaults
186+ var passwords = [ " atakatak " , " " ] // Common TAK server passwords
187+
188+ // Check if we have passwords from the preference file
189+ if filename. lowercased ( ) . contains ( " truststore " ) || filename. lowercased ( ) . contains ( " ca " ) {
190+ if let caPassword = UserDefaults . standard. string ( forKey: " lastImportCAPassword " ) {
191+ passwords. insert ( caPassword, at: 0 )
192+ }
193+ } else {
194+ if let clientPassword = UserDefaults . standard. string ( forKey: " lastImportClientPassword " ) {
195+ passwords. insert ( clientPassword, at: 0 )
196+ }
197+ }
159198
160199 for password in passwords {
161200 do {
@@ -322,9 +361,61 @@ class DataPackageImportManager: ObservableObject {
322361 // MARK: - Parse Preferences
323362
324363 private func parsePreferences( from url: URL ) async throws {
325- // Parse TAK preference files
326- // This would apply app-wide settings from the package
327364 print ( " ℹ️ Parsing preferences from: \( url. lastPathComponent) " )
365+
366+ let data = try Data ( contentsOf: url)
367+ guard let xmlString = String ( data: data, encoding: . utf8) else {
368+ throw ImportError . configParsingFailed ( " Invalid XML encoding " )
369+ }
370+
371+ // Parse TAK preference.pref format
372+ // Look for connectString entries like: "public.opentakserver.io:8089:ssl"
373+ if let connectString = extractPreferenceEntry ( from: xmlString, key: " connectString0 " ) {
374+ let components = connectString. split ( separator: " : " )
375+ if components. count >= 2 {
376+ let host = String ( components [ 0 ] )
377+ let port = UInt16 ( components [ 1 ] ) ?? 8089
378+ let useTLS = components. count >= 3 && components [ 2 ] == " ssl "
379+
380+ // Get server description if available
381+ let description = extractPreferenceEntry ( from: xmlString, key: " description0 " ) ?? " Imported Server "
382+
383+ // Get certificate passwords
384+ let clientPassword = extractPreferenceEntry ( from: xmlString, key: " clientPassword " ) ?? " atakatak "
385+ let caPassword = extractPreferenceEntry ( from: xmlString, key: " caPassword " ) ?? " atakatak "
386+
387+ // Store passwords for certificate import
388+ UserDefaults . standard. set ( clientPassword, forKey: " lastImportClientPassword " )
389+ UserDefaults . standard. set ( caPassword, forKey: " lastImportCAPassword " )
390+
391+ let server = TAKServer (
392+ name: description,
393+ host: host,
394+ port: port,
395+ protocolType: useTLS ? " ssl " : " tcp " ,
396+ useTLS: useTLS,
397+ isDefault: false ,
398+ certificateName: " administrator "
399+ )
400+
401+ serverManager. addServer ( server)
402+ print ( " ✅ Imported server from preferences: \( description) ( \( host) : \( port) , TLS: \( useTLS) ) " )
403+ }
404+ }
405+ }
406+
407+ private func extractPreferenceEntry( from xml: String , key: String ) -> String ? {
408+ // Match TAK preference format: <entry key="keyName" class="...">value</entry>
409+ let pattern = " key= \" \( key) \" [^>]*>([^<]*)</entry> "
410+ guard let regex = try ? NSRegularExpression ( pattern: pattern) else { return nil }
411+
412+ let range = NSRange ( xml. startIndex... , in: xml)
413+ guard let match = regex. firstMatch ( in: xml, range: range) ,
414+ let valueRange = Range ( match. range ( at: 1 ) , in: xml) else {
415+ return nil
416+ }
417+
418+ return String ( xml [ valueRange] )
328419 }
329420}
330421
0 commit comments