1
0
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:
John Harrington
2025-11-25 15:19:45 -07:00
parent 7e11c22779
commit 9719210a59
5 changed files with 228 additions and 247 deletions

View File

@@ -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"))]

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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)
}
}