mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
adapt chromium_importer to match conventions established by autofill:
• eliminate extern c and unsafe rust from chromium_importer logic (still called in prior napi/lib.rs) • use CommandInput / CommandResult JSON pattern to call objc methods • remove usage of libc to cleanup memory and remove this dependency
This commit is contained in:
2
apps/desktop/desktop_native/Cargo.lock
generated
2
apps/desktop/desktop_native/Cargo.lock
generated
@@ -610,9 +610,9 @@ dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
"cbc",
|
||||
"desktop_objc",
|
||||
"dirs",
|
||||
"hex",
|
||||
"libc",
|
||||
"oo7",
|
||||
"pbkdf2",
|
||||
"rand 0.9.1",
|
||||
|
||||
@@ -18,10 +18,11 @@ serde_json = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cbc = { workspace = true, features = ["alloc"] }
|
||||
desktop_objc = { path = "../objc" }
|
||||
pbkdf2 = "=0.12.2"
|
||||
security-framework = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
sha1 = "=0.10.6"
|
||||
tokio = { workspace = true, features = ["rt"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
aes-gcm = { workspace = true }
|
||||
|
||||
@@ -84,9 +84,9 @@ pub fn get_available_profiles(browser_name: &str) -> Result<Vec<ProfileInfo>> {
|
||||
/// Request access to browser directory (MAS builds only)
|
||||
/// This shows the permission dialog and creates a security-scoped bookmark
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn request_browser_access(browser_name: &str, mas_build: bool) -> Result<()> {
|
||||
pub async fn request_browser_access(browser_name: &str, mas_build: bool) -> Result<()> {
|
||||
if mas_build {
|
||||
platform::sandbox::ScopedBrowserAccess::request_only(browser_name)?;
|
||||
platform::sandbox::ScopedBrowserAccess::request_only(browser_name).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -101,7 +101,7 @@ pub async fn import_logins(
|
||||
let _access = if _mas_build {
|
||||
Some(platform::sandbox::ScopedBrowserAccess::resume(
|
||||
browser_name,
|
||||
)?)
|
||||
).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -12,18 +12,41 @@ use crate::{
|
||||
//
|
||||
|
||||
pub mod sandbox {
|
||||
use std::{ffi::CString, os::raw::c_char};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
extern "C" {
|
||||
fn requestBrowserAccess(
|
||||
browser_name: *const c_char,
|
||||
relative_path: *const c_char,
|
||||
) -> *mut c_char;
|
||||
fn hasStoredBrowserAccess(browser_name: *const c_char) -> bool;
|
||||
fn startBrowserAccess(browser_name: *const c_char) -> *mut c_char;
|
||||
fn stopBrowserAccess(browser_name: *const c_char);
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
enum CommandResult<T> {
|
||||
#[serde(rename = "success")]
|
||||
Success { value: T },
|
||||
#[serde(rename = "error")]
|
||||
Error { error: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RequestAccessResponse {
|
||||
#[allow(dead_code)]
|
||||
bookmark: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct HasStoredAccessResponse {
|
||||
#[serde(rename = "hasAccess")]
|
||||
has_access: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct StartAccessResponse {
|
||||
#[allow(dead_code)]
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct CommandInput {
|
||||
namespace: String,
|
||||
command: String,
|
||||
params: serde_json::Value,
|
||||
}
|
||||
|
||||
pub struct ScopedBrowserAccess {
|
||||
@@ -32,52 +55,105 @@ pub mod sandbox {
|
||||
|
||||
impl ScopedBrowserAccess {
|
||||
/// Request access to browser directory and create a security bookmark if access is approved
|
||||
pub fn request_only(browser_name: &str) -> Result<()> {
|
||||
pub async fn request_only(browser_name: &str) -> Result<()> {
|
||||
let config = crate::chromium::platform::SUPPORTED_BROWSERS
|
||||
.iter()
|
||||
.find(|b| b.name == browser_name)
|
||||
.ok_or_else(|| anyhow!("Unsupported browser: {}", browser_name))?;
|
||||
|
||||
let c_name = CString::new(browser_name)?;
|
||||
let c_path = CString::new(config.data_dir)?;
|
||||
let input = CommandInput {
|
||||
namespace: "chromium_importer".to_string(),
|
||||
command: "request_access".to_string(),
|
||||
params: serde_json::json!({
|
||||
"browserName": browser_name,
|
||||
"relativePath": config.data_dir,
|
||||
}),
|
||||
};
|
||||
|
||||
let bookmark_ptr = unsafe { requestBrowserAccess(c_name.as_ptr(), c_path.as_ptr()) };
|
||||
if bookmark_ptr.is_null() {
|
||||
return Err(anyhow!(
|
||||
"User denied access or selected an invalid browser directory"
|
||||
));
|
||||
let output = desktop_objc::run_command(serde_json::to_string(&input)?)
|
||||
.await
|
||||
.map_err(|e| anyhow!("Failed to call ObjC command: {}", e))?;
|
||||
|
||||
let result: CommandResult<RequestAccessResponse> = serde_json::from_str(&output)
|
||||
.map_err(|e| anyhow!("Failed to parse ObjC response: {}", e))?;
|
||||
|
||||
match result {
|
||||
CommandResult::Success { .. } => Ok(()),
|
||||
CommandResult::Error { error } => Err(anyhow!("{}", error)),
|
||||
}
|
||||
unsafe { libc::free(bookmark_ptr as *mut libc::c_void) };
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resume browser directory access using previously created security bookmark
|
||||
pub fn resume(browser_name: &str) -> Result<Self> {
|
||||
let c_name = CString::new(browser_name)?;
|
||||
pub async fn resume(browser_name: &str) -> Result<Self> {
|
||||
// First check if we have stored access
|
||||
let has_access_input = CommandInput {
|
||||
namespace: "chromium_importer".to_string(),
|
||||
command: "has_stored_access".to_string(),
|
||||
params: serde_json::json!({
|
||||
"browserName": browser_name,
|
||||
}),
|
||||
};
|
||||
|
||||
if !unsafe { hasStoredBrowserAccess(c_name.as_ptr()) } {
|
||||
let has_access_output = desktop_objc::run_command(serde_json::to_string(&has_access_input)?)
|
||||
.await
|
||||
.map_err(|e| anyhow!("Failed to call ObjC command: {}", e))?;
|
||||
|
||||
let has_access_result: CommandResult<HasStoredAccessResponse> =
|
||||
serde_json::from_str(&has_access_output)
|
||||
.map_err(|e| anyhow!("Failed to parse ObjC response: {}", e))?;
|
||||
|
||||
let has_access = match has_access_result {
|
||||
CommandResult::Success { value } => value.has_access,
|
||||
CommandResult::Error { error } => return Err(anyhow!("{}", error)),
|
||||
};
|
||||
|
||||
if !has_access {
|
||||
return Err(anyhow!("Access has not been granted for this browser"));
|
||||
}
|
||||
|
||||
let path_ptr = unsafe { startBrowserAccess(c_name.as_ptr()) };
|
||||
if path_ptr.is_null() {
|
||||
return Err(anyhow!("Existing security scoped access has become stale"));
|
||||
}
|
||||
unsafe { libc::free(path_ptr as *mut libc::c_void) };
|
||||
// Start accessing the browser
|
||||
let start_input = CommandInput {
|
||||
namespace: "chromium_importer".to_string(),
|
||||
command: "start_access".to_string(),
|
||||
params: serde_json::json!({
|
||||
"browserName": browser_name,
|
||||
}),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
browser_name: browser_name.to_string(),
|
||||
})
|
||||
let start_output = desktop_objc::run_command(serde_json::to_string(&start_input)?)
|
||||
.await
|
||||
.map_err(|e| anyhow!("Failed to call ObjC command: {}", e))?;
|
||||
|
||||
let start_result: CommandResult<StartAccessResponse> =
|
||||
serde_json::from_str(&start_output)
|
||||
.map_err(|e| anyhow!("Failed to parse ObjC response: {}", e))?;
|
||||
|
||||
match start_result {
|
||||
CommandResult::Success { .. } => Ok(Self {
|
||||
browser_name: browser_name.to_string(),
|
||||
}),
|
||||
CommandResult::Error { error } => Err(anyhow!("{}", error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ScopedBrowserAccess {
|
||||
fn drop(&mut self) {
|
||||
let Ok(c_name) = CString::new(self.browser_name.as_str()) else {
|
||||
return;
|
||||
};
|
||||
unsafe { stopBrowserAccess(c_name.as_ptr()) };
|
||||
let browser_name = self.browser_name.clone();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
let input = CommandInput {
|
||||
namespace: "chromium_importer".to_string(),
|
||||
command: "stop_access".to_string(),
|
||||
params: serde_json::json!({
|
||||
"browserName": browser_name,
|
||||
}),
|
||||
};
|
||||
|
||||
if let Ok(input_json) = serde_json::to_string(&input) {
|
||||
let _ = desktop_objc::run_command(input_json).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
apps/desktop/desktop_native/napi/index.d.ts
vendored
2
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -256,7 +256,7 @@ export declare namespace chromium_importer {
|
||||
export function getMetadata(masBuild: boolean): Record<string, NativeImporterMetadata>
|
||||
export function getAvailableProfiles(browser: string): Array<ProfileInfo>
|
||||
export function importLogins(browser: string, profileId: string, masBuild: boolean): Promise<Array<LoginImportResult>>
|
||||
export function requestBrowserAccess(browser: string, masBuild: boolean): void
|
||||
export function requestBrowserAccess(browser: string, masBuild: boolean): Promise<void>
|
||||
}
|
||||
export declare namespace autotype {
|
||||
export function getForegroundWindowTitle(): string
|
||||
|
||||
@@ -1195,17 +1195,14 @@ pub mod chromium_importer {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn request_browser_access(_browser: String, _mas_build: bool) -> napi::Result<()> {
|
||||
pub async fn request_browser_access(_browser: String, _mas_build: bool) -> napi::Result<()> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
chromium_importer::chromium::request_browser_access(&_browser, _mas_build)
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
return chromium_importer::chromium::request_browser_access(&_browser, _mas_build)
|
||||
.await
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()));
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
// No-op outside of Mac OS
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef BROWSER_ACCESS_H
|
||||
#define BROWSER_ACCESS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// Request user permission to access browser directory
|
||||
// Returns base64-encoded bookmark data, or NULL if declined
|
||||
// Caller must free returned string
|
||||
char* requestBrowserAccess(const char* browserName, const char* relativePath);
|
||||
|
||||
// Check if we have stored bookmark (doesn't verify validity)
|
||||
bool hasStoredBrowserAccess(const char* browserName);
|
||||
|
||||
// Start accessing browser using stored bookmark
|
||||
// Returns resolved path, or NULL if bookmark invalid
|
||||
// Caller must free returned string and call stopBrowserAccess when done
|
||||
char* startBrowserAccess(const char* browserName);
|
||||
|
||||
// Stop accessing browser (MUST be called after startBrowserAccess)
|
||||
void stopBrowserAccess(const char* browserName);
|
||||
|
||||
#endif
|
||||
@@ -1,56 +0,0 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "browser_access.h"
|
||||
#import "../utils.h"
|
||||
|
||||
#import "browser_access_manager.h"
|
||||
|
||||
static BrowserAccessManager* sharedManager = nil;
|
||||
|
||||
static BrowserAccessManager* getManager() {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedManager = [[BrowserAccessManager alloc] init];
|
||||
});
|
||||
return sharedManager;
|
||||
}
|
||||
|
||||
char* requestBrowserAccess(const char* browserName, const char* relativePath) {
|
||||
@autoreleasepool {
|
||||
NSString* name = [NSString stringWithUTF8String:browserName];
|
||||
NSString* path = [NSString stringWithUTF8String:relativePath];
|
||||
NSString* result = [getManager() requestAccessToBrowserDir:name relativePath:path];
|
||||
|
||||
if (result == nil) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return strdup([result UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasStoredBrowserAccess(const char* browserName) {
|
||||
@autoreleasepool {
|
||||
NSString* name = [NSString stringWithUTF8String:browserName];
|
||||
return [getManager() hasStoredAccess:name];
|
||||
}
|
||||
}
|
||||
|
||||
char* startBrowserAccess(const char* browserName) {
|
||||
@autoreleasepool {
|
||||
NSString* name = [NSString stringWithUTF8String:browserName];
|
||||
NSString* result = [getManager() startAccessingBrowser:name];
|
||||
|
||||
if (result == nil) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return strdup([result UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
void stopBrowserAccess(const char* browserName) {
|
||||
@autoreleasepool {
|
||||
NSString* name = [NSString stringWithUTF8String:browserName];
|
||||
[getManager() stopAccessingBrowser:name];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef HAS_STORED_ACCESS_H
|
||||
#define HAS_STORED_ACCESS_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void hasStoredAccessCommand(void *context, NSDictionary *params);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "../../interop.h"
|
||||
#import "../browser_access_manager.h"
|
||||
#import "has_stored_access.h"
|
||||
|
||||
void hasStoredAccessCommand(void* context, NSDictionary *params) {
|
||||
NSString *browserName = params[@"browserName"];
|
||||
|
||||
if (!browserName) {
|
||||
return _return(context, _error(@"Missing required parameter: browserName"));
|
||||
}
|
||||
|
||||
BrowserAccessManager *manager = [[BrowserAccessManager alloc] init];
|
||||
BOOL hasAccess = [manager hasStoredAccess:browserName];
|
||||
|
||||
_return(context, _success(@{@"hasAccess": @(hasAccess)}));
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef REQUEST_ACCESS_H
|
||||
#define REQUEST_ACCESS_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void requestAccessCommand(void *context, NSDictionary *params);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "../../interop.h"
|
||||
#import "../browser_access_manager.h"
|
||||
#import "request_access.h"
|
||||
|
||||
void requestAccessCommand(void* context, NSDictionary *params) {
|
||||
NSString *browserName = params[@"browserName"];
|
||||
NSString *relativePath = params[@"relativePath"];
|
||||
|
||||
if (!browserName || !relativePath) {
|
||||
return _return(context, _error(@"Missing required parameters: browserName and relativePath"));
|
||||
}
|
||||
|
||||
BrowserAccessManager *manager = [[BrowserAccessManager alloc] init];
|
||||
NSString *bookmarkData = [manager requestAccessToBrowserDir:browserName relativePath:relativePath];
|
||||
|
||||
if (bookmarkData == nil) {
|
||||
return _return(context, _error(@"User denied access or selected an invalid browser directory"));
|
||||
}
|
||||
|
||||
_return(context, _success(@{@"bookmark": bookmarkData}));
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef START_ACCESS_H
|
||||
#define START_ACCESS_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void startAccessCommand(void *context, NSDictionary *params);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,21 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "../../interop.h"
|
||||
#import "../browser_access_manager.h"
|
||||
#import "start_access.h"
|
||||
|
||||
void startAccessCommand(void* context, NSDictionary *params) {
|
||||
NSString *browserName = params[@"browserName"];
|
||||
|
||||
if (!browserName) {
|
||||
return _return(context, _error(@"Missing required parameter: browserName"));
|
||||
}
|
||||
|
||||
BrowserAccessManager *manager = [[BrowserAccessManager alloc] init];
|
||||
NSString *resolvedPath = [manager startAccessingBrowser:browserName];
|
||||
|
||||
if (resolvedPath == nil) {
|
||||
return _return(context, _error(@"Failed to start accessing browser. Bookmark may be invalid or revoked"));
|
||||
}
|
||||
|
||||
_return(context, _success(@{@"path": resolvedPath}));
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef STOP_ACCESS_H
|
||||
#define STOP_ACCESS_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void stopAccessCommand(void *context, NSDictionary *params);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "../../interop.h"
|
||||
#import "../browser_access_manager.h"
|
||||
#import "stop_access.h"
|
||||
|
||||
void stopAccessCommand(void* context, NSDictionary *params) {
|
||||
NSString *browserName = params[@"browserName"];
|
||||
|
||||
if (!browserName) {
|
||||
return _return(context, _error(@"Missing required parameter: browserName"));
|
||||
}
|
||||
|
||||
BrowserAccessManager *manager = [[BrowserAccessManager alloc] init];
|
||||
[manager stopAccessingBrowser:browserName];
|
||||
|
||||
_return(context, _success(@{}));
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef RUN_CHROMIUM_COMMAND_H
|
||||
#define RUN_CHROMIUM_COMMAND_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void runChromiumCommand(void* context, NSDictionary *input);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,25 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "commands/request_access.h"
|
||||
#import "commands/has_stored_access.h"
|
||||
#import "commands/start_access.h"
|
||||
#import "commands/stop_access.h"
|
||||
#import "../interop.h"
|
||||
#import "../utils.h"
|
||||
#import "run_chromium_command.h"
|
||||
|
||||
void runChromiumCommand(void* context, NSDictionary *input) {
|
||||
NSString *command = input[@"command"];
|
||||
NSDictionary *params = input[@"params"];
|
||||
|
||||
if ([command isEqual:@"request_access"]) {
|
||||
return requestAccessCommand(context, params);
|
||||
} else if ([command isEqual:@"has_stored_access"]) {
|
||||
return hasStoredAccessCommand(context, params);
|
||||
} else if ([command isEqual:@"start_access"]) {
|
||||
return startAccessCommand(context, params);
|
||||
} else if ([command isEqual:@"stop_access"]) {
|
||||
return stopAccessCommand(context, params);
|
||||
}
|
||||
|
||||
_return(context, _error([NSString stringWithFormat:@"Unknown command: %@", command]));
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "autofill/run_autofill_command.h"
|
||||
#import "chromium_importer/run_chromium_command.h"
|
||||
#import "interop.h"
|
||||
#import "utils.h"
|
||||
|
||||
@@ -8,6 +9,8 @@ void pickAndRunCommand(void* context, NSDictionary *input) {
|
||||
|
||||
if ([namespace isEqual:@"autofill"]) {
|
||||
return runAutofillCommand(context, input);
|
||||
} else if ([namespace isEqual:@"chromium_importer"]) {
|
||||
return runChromiumCommand(context, input);
|
||||
}
|
||||
|
||||
_return(context, _error([NSString stringWithFormat:@"Unknown namespace: %@", namespace]));
|
||||
|
||||
Reference in New Issue
Block a user