mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 05:13:29 +00:00
replace swift logic with objc
This commit is contained in:
@@ -1,54 +1,8 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
fn main() {
|
||||
use std::process::Command;
|
||||
|
||||
use glob::glob;
|
||||
|
||||
let out_dir = std::env::var("OUT_DIR").expect("env var OUT_DIR is invalid or not set");
|
||||
|
||||
// Compile Swift files FIRST (generates Bitwarden-Swift.h for browser_access.m)
|
||||
let swift_files: Vec<String> = glob("src/native/**/*.swift")
|
||||
.expect("Failed to read Swift glob pattern")
|
||||
.filter_map(Result::ok)
|
||||
.filter_map(|p| {
|
||||
println!("cargo::rerun-if-changed={}", p.display());
|
||||
p.to_str().map(|s| s.to_string())
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !swift_files.is_empty() {
|
||||
// Compile Swift into a static library
|
||||
let status = Command::new("swiftc")
|
||||
.args([
|
||||
"-emit-library",
|
||||
"-static",
|
||||
"-module-name",
|
||||
"Bitwarden",
|
||||
"-import-objc-header",
|
||||
"src/native/bridging-header.h",
|
||||
"-emit-objc-header-path",
|
||||
&format!("{}/Bitwarden-Swift.h", out_dir),
|
||||
"-o",
|
||||
&format!("{}/libbitwarden_swift.a", out_dir),
|
||||
])
|
||||
.args(&swift_files)
|
||||
.status()
|
||||
.expect("Failed to compile Swift code");
|
||||
|
||||
if !status.success() {
|
||||
panic!("Swift compilation failed");
|
||||
}
|
||||
|
||||
// Tell cargo to link the Swift library
|
||||
println!("cargo:rustc-link-search=native={}", out_dir);
|
||||
println!("cargo:rustc-link-lib=static=bitwarden_swift");
|
||||
|
||||
// Link required Swift/Foundation frameworks
|
||||
println!("cargo:rustc-link-lib=framework=Foundation");
|
||||
println!("cargo:rustc-link-lib=framework=AppKit");
|
||||
}
|
||||
|
||||
// Compile Objective-C files (Bitwarden-Swift.h exists now)
|
||||
// Compile Objective-C files
|
||||
let mut builder = cc::Build::new();
|
||||
|
||||
// Compile all .m files in the src/native directory
|
||||
@@ -59,9 +13,12 @@ fn main() {
|
||||
}
|
||||
|
||||
builder
|
||||
.include(&out_dir) // Add OUT_DIR to include path so Bitwarden-Swift.h can be found
|
||||
.flag("-fobjc-arc") // Enable Auto Reference Counting (ARC)
|
||||
.compile("objc_code");
|
||||
|
||||
// Link required frameworks
|
||||
println!("cargo:rustc-link-lib=framework=Foundation");
|
||||
println!("cargo:rustc-link-lib=framework=AppKit");
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface BrowserAccessManager : NSObject
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
/// Request access to a specific browser's directory
|
||||
/// Returns security bookmark data (used to persist permissions) as base64 string, or nil if user declined
|
||||
- (nullable NSString *)requestAccessToBrowserDir:(NSString *)browserName;
|
||||
|
||||
/// Check if we have stored bookmark for browser (doesn't verify it's still valid)
|
||||
- (BOOL)hasStoredAccess:(NSString *)browserName;
|
||||
|
||||
/// Start accessing a browser directory using stored bookmark
|
||||
/// Returns the resolved path, or nil if bookmark is invalid/revoked
|
||||
- (nullable NSString *)startAccessingBrowser:(NSString *)browserName;
|
||||
|
||||
/// Stop accessing a browser directory (must be called after startAccessingBrowser)
|
||||
- (void)stopAccessingBrowser:(NSString *)browserName;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,196 @@
|
||||
#import "BrowserAccessManager.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@implementation BrowserAccessManager {
|
||||
NSString *_bookmarkKey;
|
||||
NSDictionary<NSString *, NSString *> *_browserPaths;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_bookmarkKey = @"com.bitwarden.chromiumImporter.bookmarks";
|
||||
|
||||
_browserPaths = @{
|
||||
@"Chrome": @"Library/Application Support/Google/Chrome",
|
||||
@"Chromium": @"Library/Application Support/Chromium",
|
||||
@"Microsoft Edge": @"Library/Application Support/Microsoft Edge",
|
||||
@"Brave": @"Library/Application Support/BraveSoftware/Brave-Browser",
|
||||
@"Arc": @"Library/Application Support/Arc/User Data",
|
||||
@"Opera": @"Library/Application Support/com.operasoftware.Opera",
|
||||
@"Vivaldi": @"Library/Application Support/Vivaldi"
|
||||
};
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)requestAccessToBrowserDir:(NSString *)browserName {
|
||||
// NSLog(@"[OBJC] requestAccessToBrowserDir called for: %@", browserName);
|
||||
|
||||
NSString *relativePath = _browserPaths[browserName];
|
||||
if (!relativePath) {
|
||||
// NSLog(@"[OBJC] Unknown browser: %@", browserName);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSURL *homeDir = [[NSFileManager defaultManager] homeDirectoryForCurrentUser];
|
||||
NSURL *browserPath = [homeDir URLByAppendingPathComponent:relativePath];
|
||||
|
||||
// NSLog(@"[OBJC] Browser path: %@", browserPath.path);
|
||||
|
||||
// NSOpenPanel must be run on the main thread
|
||||
__block NSURL *selectedURL = nil;
|
||||
__block NSModalResponse panelResult = NSModalResponseCancel;
|
||||
|
||||
void (^showPanel)(void) = ^{
|
||||
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
|
||||
openPanel.message = [NSString stringWithFormat:
|
||||
@"Please select your %@ data folder\n\nExpected location:\n%@",
|
||||
browserName, browserPath.path];
|
||||
openPanel.prompt = @"Grant Access";
|
||||
openPanel.allowsMultipleSelection = NO;
|
||||
openPanel.canChooseDirectories = YES;
|
||||
openPanel.canChooseFiles = NO;
|
||||
openPanel.directoryURL = browserPath;
|
||||
|
||||
// NSLog(@"[OBJC] About to call runModal");
|
||||
panelResult = [openPanel runModal];
|
||||
selectedURL = openPanel.URL;
|
||||
// NSLog(@"[OBJC] runModal returned: %ld", (long)panelResult);
|
||||
};
|
||||
|
||||
if ([NSThread isMainThread]) {
|
||||
// NSLog(@"[OBJC] Already on main thread");
|
||||
showPanel();
|
||||
} else {
|
||||
// NSLog(@"[OBJC] Dispatching to main queue...");
|
||||
dispatch_sync(dispatch_get_main_queue(), showPanel);
|
||||
}
|
||||
|
||||
if (panelResult != NSModalResponseOK || !selectedURL) {
|
||||
// NSLog(@"[OBJC] User cancelled access request or panel failed");
|
||||
return nil;
|
||||
}
|
||||
|
||||
// NSLog(@"[OBJC] User selected URL: %@", selectedURL.path);
|
||||
|
||||
NSURL *localStatePath = [selectedURL URLByAppendingPathComponent:@"Local State"];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:localStatePath.path]) {
|
||||
// NSLog(@"[OBJC] Selected folder doesn't appear to be a valid %@ directory", browserName);
|
||||
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = @"Invalid Folder";
|
||||
alert.informativeText = [NSString stringWithFormat:
|
||||
@"The selected folder doesn't appear to be a valid %@ data directory. Please select the correct folder.",
|
||||
browserName];
|
||||
alert.alertStyle = NSAlertStyleWarning;
|
||||
[alert runModal];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Access is temporary right now, persist it by creating a security bookmark
|
||||
NSError *error = nil;
|
||||
NSData *bookmarkData = [selectedURL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
|
||||
includingResourceValuesForKeys:nil
|
||||
relativeToURL:nil
|
||||
error:&error];
|
||||
|
||||
if (!bookmarkData) {
|
||||
// NSLog(@"[OBJC] Failed to create bookmark: %@", error);
|
||||
return nil;
|
||||
}
|
||||
|
||||
[self saveBookmark:bookmarkData forBrowser:browserName];
|
||||
// NSLog(@"[OBJC] Successfully created and saved bookmark");
|
||||
return [bookmarkData base64EncodedStringWithOptions:0];
|
||||
}
|
||||
|
||||
- (BOOL)hasStoredAccess:(NSString *)browserName {
|
||||
return [self loadBookmarkForBrowser:browserName] != nil;
|
||||
}
|
||||
|
||||
- (NSString *)startAccessingBrowser:(NSString *)browserName {
|
||||
NSData *bookmarkData = [self loadBookmarkForBrowser:browserName];
|
||||
if (!bookmarkData) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
BOOL isStale = NO;
|
||||
NSError *error = nil;
|
||||
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmarkData
|
||||
options:NSURLBookmarkResolutionWithSecurityScope
|
||||
relativeToURL:nil
|
||||
bookmarkDataIsStale:&isStale
|
||||
error:&error];
|
||||
|
||||
if (!url) {
|
||||
// NSLog(@"Failed to resolve bookmark: %@", error);
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (isStale) {
|
||||
// NSLog(@"Security bookmark for %@ is stale, attempting to re-create it", browserName);
|
||||
NSData *newBookmarkData = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
|
||||
includingResourceValuesForKeys:nil
|
||||
relativeToURL:nil
|
||||
error:&error];
|
||||
|
||||
if (!newBookmarkData) {
|
||||
// NSLog(@"Failed to create bookmark: %@", error);
|
||||
return nil;
|
||||
}
|
||||
|
||||
[self saveBookmark:newBookmarkData forBrowser:browserName];
|
||||
}
|
||||
|
||||
if (![url startAccessingSecurityScopedResource]) {
|
||||
// NSLog(@"Failed to start accessing security-scoped resource");
|
||||
return nil;
|
||||
}
|
||||
|
||||
return url.path;
|
||||
}
|
||||
|
||||
- (void)stopAccessingBrowser:(NSString *)browserName {
|
||||
NSData *bookmarkData = [self loadBookmarkForBrowser:browserName];
|
||||
if (!bookmarkData) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL isStale = NO;
|
||||
NSError *error = nil;
|
||||
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmarkData
|
||||
options:NSURLBookmarkResolutionWithSecurityScope
|
||||
relativeToURL:nil
|
||||
bookmarkDataIsStale:&isStale
|
||||
error:&error];
|
||||
|
||||
if (!url) {
|
||||
// NSLog(@"Failed to resolve bookmark for stop: %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
[url stopAccessingSecurityScopedResource];
|
||||
}
|
||||
|
||||
#pragma mark - Private Methods
|
||||
|
||||
- (NSString *)bookmarkKeyFor:(NSString *)browserName {
|
||||
return [NSString stringWithFormat:@"%@.%@", _bookmarkKey, browserName];
|
||||
}
|
||||
|
||||
- (void)saveBookmark:(NSData *)data forBrowser:(NSString *)browserName {
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSString *key = [self bookmarkKeyFor:browserName];
|
||||
[defaults setObject:data forKey:key];
|
||||
[defaults synchronize];
|
||||
}
|
||||
|
||||
- (NSData *)loadBookmarkForBrowser:(NSString *)browserName {
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSString *key = [self bookmarkKeyFor:browserName];
|
||||
return [defaults dataForKey:key];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -2,7 +2,7 @@
|
||||
#import "browser_access.h"
|
||||
#import "../utils.h"
|
||||
|
||||
#import "Bitwarden-Swift.h"
|
||||
#import "BrowserAccessManager.h"
|
||||
|
||||
static BrowserAccessManager* sharedManager = nil;
|
||||
|
||||
@@ -17,8 +17,7 @@ static BrowserAccessManager* getManager() {
|
||||
char* requestBrowserAccess(const char* browserName) {
|
||||
@autoreleasepool {
|
||||
NSString* name = [NSString stringWithUTF8String:browserName];
|
||||
// Note: Matches the Swift method name with typo "Broswer"
|
||||
NSString* result = [getManager() requestAccessToBroswerDir:name];
|
||||
NSString* result = [getManager() requestAccessToBrowserDir:name];
|
||||
|
||||
if (result == nil) {
|
||||
return NULL;
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
import Cocoa
|
||||
import Foundation
|
||||
|
||||
@objc public class BrowserAccessManager: NSObject {
|
||||
private let bookmarkKey = "com.bitwarden.chromiumImporter.bookmarks"
|
||||
|
||||
private let browserPaths: [String: String] = [
|
||||
"Chrome": "Library/Application Support/Google/Chrome",
|
||||
"Chromium": "Library/Application Support/Chromium",
|
||||
"Microsoft Edge": "Library/Application Support/Microsoft Edge",
|
||||
"Brave": "Library/Application Support/BraveSoftware/Brave-Browser",
|
||||
"Arc": "Library/Application Support/Arc/User Data",
|
||||
"Opera": "Library/Application Support/com.operasoftware.Opera",
|
||||
"Vivaldi": "Library/Application Support/Vivaldi",
|
||||
]
|
||||
|
||||
/// Request access to a specific browser's directory
|
||||
/// Returns security bookmark data (used to persist permissions) as base64 string, or nil if user declined
|
||||
@objc public func requestAccessToBroswerDir(_ browserName: String) -> String? {
|
||||
// NSLog("[SWIFT] requestAccessToBroswerDir called for: \(browserName)")
|
||||
|
||||
guard let relativePath = browserPaths[browserName] else {
|
||||
// NSLog("[SWIFT] Unknown browser: \(browserName)")
|
||||
return nil
|
||||
}
|
||||
|
||||
let homeDir = FileManager.default.homeDirectoryForCurrentUser
|
||||
let browserPath = homeDir.appendingPathComponent(relativePath)
|
||||
|
||||
// NSLog("[SWIFT] Browser path: \(browserPath.path)")
|
||||
|
||||
// NSOpenPanel must be run on the main thread
|
||||
var selectedURL: URL?
|
||||
var panelResult: NSApplication.ModalResponse = .cancel
|
||||
|
||||
if Thread.isMainThread {
|
||||
// NSLog("[SWIFT] Already on main thread")
|
||||
let openPanel = NSOpenPanel()
|
||||
openPanel.message =
|
||||
"Please select your \(browserName) data folder\n\nExpected location:\n\(browserPath.path)"
|
||||
openPanel.prompt = "Grant Access"
|
||||
openPanel.allowsMultipleSelection = false
|
||||
openPanel.canChooseDirectories = true
|
||||
openPanel.canChooseFiles = false
|
||||
openPanel.directoryURL = browserPath
|
||||
|
||||
// NSLog("[SWIFT] About to call openPanel.runModal()")
|
||||
panelResult = openPanel.runModal()
|
||||
selectedURL = openPanel.url
|
||||
// NSLog("[SWIFT] runModal returned: \(panelResult.rawValue)")
|
||||
} else {
|
||||
// NSLog("[SWIFT] Dispatching to main queue...")
|
||||
DispatchQueue.main.sync {
|
||||
// NSLog("[SWIFT] Inside main queue dispatch block")
|
||||
let openPanel = NSOpenPanel()
|
||||
openPanel.message =
|
||||
"Please select your \(browserName) data folder\n\nExpected location:\n\(browserPath.path)"
|
||||
openPanel.prompt = "Grant Access"
|
||||
openPanel.allowsMultipleSelection = false
|
||||
openPanel.canChooseDirectories = true
|
||||
openPanel.canChooseFiles = false
|
||||
openPanel.directoryURL = browserPath
|
||||
|
||||
// NSLog("[SWIFT] About to call openPanel.runModal()")
|
||||
panelResult = openPanel.runModal()
|
||||
selectedURL = openPanel.url
|
||||
// NSLog("[SWIFT] runModal returned: \(panelResult.rawValue)")
|
||||
}
|
||||
}
|
||||
|
||||
guard panelResult == .OK, let url = selectedURL else {
|
||||
// NSLog("[SWIFT] User cancelled access request or panel failed")
|
||||
return nil
|
||||
}
|
||||
|
||||
// NSLog("[SWIFT] User selected URL: \(url.path)")
|
||||
|
||||
let localStatePath = url.appendingPathComponent("Local State")
|
||||
guard FileManager.default.fileExists(atPath: localStatePath.path) else {
|
||||
// NSLog("[SWIFT] Selected folder doesn't appear to be a valid \(browserName) directory")
|
||||
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Invalid Folder"
|
||||
alert.informativeText =
|
||||
"The selected folder doesn't appear to be a valid \(browserName) data directory. Please select the correct folder."
|
||||
alert.alertStyle = .warning
|
||||
alert.runModal()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// access is temporary right now, persist it by creating a security bookmark
|
||||
do {
|
||||
let bookmarkData = try url.bookmarkData(
|
||||
options: .withSecurityScope,
|
||||
includingResourceValuesForKeys: nil,
|
||||
relativeTo: nil
|
||||
)
|
||||
|
||||
saveBookmark(bookmarkData, forBrowser: browserName)
|
||||
// NSLog("[SWIFT] Successfully created and saved bookmark")
|
||||
return bookmarkData.base64EncodedString()
|
||||
} catch {
|
||||
// NSLog("[SWIFT] Failed to create bookmark: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if we have stored bookmark for browser (doesn't verify it's still valid)
|
||||
@objc public func hasStoredAccess(_ browserName: String) -> Bool {
|
||||
return loadBookmark(forBrowser: browserName) != nil
|
||||
}
|
||||
|
||||
/// Start accessing a browser directory using stored bookmark
|
||||
/// Returns the resolved path, or nil if bookmark is invalid/revoked
|
||||
@objc public func startAccessingBrowser(_ browserName: String) -> String? {
|
||||
guard let bookmarkData = loadBookmark(forBrowser: browserName) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
var isStale = false
|
||||
let url = try URL(
|
||||
resolvingBookmarkData: bookmarkData,
|
||||
options: .withSecurityScope,
|
||||
relativeTo: nil,
|
||||
bookmarkDataIsStale: &isStale
|
||||
)
|
||||
|
||||
if isStale {
|
||||
// NSLog("Security bookmark for \(browserName) is stale, attempting to re-create it")
|
||||
do {
|
||||
let newBookmarkData = try url.bookmarkData(
|
||||
options: .withSecurityScope,
|
||||
includingResourceValuesForKeys: nil,
|
||||
relativeTo: nil
|
||||
)
|
||||
|
||||
saveBookmark(newBookmarkData, forBrowser: browserName)
|
||||
} catch {
|
||||
// NSLog("Failed to create bookmark: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
guard url.startAccessingSecurityScopedResource() else {
|
||||
// NSLog("Failed to start accessing security-scoped resource")
|
||||
return nil
|
||||
}
|
||||
|
||||
return url.path
|
||||
} catch {
|
||||
// NSLog("Failed to resolve bookmark: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop accessing a browser directory (must be called after startAccessingBrowser)
|
||||
@objc public func stopAccessingBrowser(_ browserName: String) {
|
||||
guard let bookmarkData = loadBookmark(forBrowser: browserName) else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
var isStale = false
|
||||
let url = try URL(
|
||||
resolvingBookmarkData: bookmarkData,
|
||||
options: .withSecurityScope,
|
||||
relativeTo: nil,
|
||||
bookmarkDataIsStale: &isStale
|
||||
)
|
||||
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
} catch {
|
||||
// NSLog("Failed to resolve bookmark for stop: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func bookmarkKeyFor(_ browserName: String) -> String {
|
||||
return "\(bookmarkKey).\(browserName)"
|
||||
}
|
||||
|
||||
private func saveBookmark(_ data: Data, forBrowser browserName: String) {
|
||||
let defaults = UserDefaults.standard
|
||||
let key = bookmarkKeyFor(browserName)
|
||||
defaults.set(data, forKey: key)
|
||||
defaults.synchronize()
|
||||
}
|
||||
|
||||
private func loadBookmark(forBrowser browserName: String) -> Data? {
|
||||
let defaults = UserDefaults.standard
|
||||
let key = bookmarkKeyFor(browserName)
|
||||
return defaults.data(forKey: key)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user