Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/runtime/webcore/Blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1321,10 +1321,10 @@ impl BlobExt for Blob {

validate_writable_blob(global_this, self)?;

let store = self.store().expect("infallible: store present");
let store = self.store().expect("infallible: store present").clone();
match &store.data {
store::Data::S3(s3) => s3.unlink(
store,
store.clone(),
&global_this.js_thread_of_caller(callframe),
args.next_eat(),
),
Expand Down
23 changes: 12 additions & 11 deletions src/runtime/webcore/S3Client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,13 +427,13 @@ impl S3Client {
global: &JSGlobalObject,
callframe: &CallFrame,
) -> JsResult<JSValue> {
let (mut blob, options) = ptr.blob_and_options(
let (blob, options) = ptr.blob_and_options(
global,
callframe,
"presign",
MissingPathError::MissingOrInvalid,
)?;
S3File::get_presign_url_from(&mut blob, global, options)
S3File::get_presign_url_from(&blob, global, options)
}

#[bun_jsc::host_fn(method)]
Expand Down Expand Up @@ -550,9 +550,9 @@ impl S3Client {
ptr.request_payer,
)?;

let store = blob.store.get().as_ref().unwrap();
let store = blob.store.get().as_ref().unwrap().clone();
store.data.as_s3().list_objects(
store,
store.clone(),
&global.js_thread_of_caller(callframe),
object_keys,
options,
Expand All @@ -571,11 +571,12 @@ impl S3Client {
"unlink",
MissingPathError::AlwaysMissingArgs,
)?;
let store = blob.store.get().as_ref().unwrap();
store
.data
.as_s3()
.unlink(store, &global.js_thread_of_caller(callframe), options)
let store = blob.store.get().as_ref().unwrap().clone();
store.data.as_s3().unlink(
store.clone(),
&global.js_thread_of_caller(callframe),
options,
)
}

// ── Static methods ────────────────────────────────────────────────────
Expand Down Expand Up @@ -658,9 +659,9 @@ impl S3Client {
&existing_credentials,
)?;

let store = blob.store.get().as_ref().unwrap();
let store = blob.store.get().as_ref().unwrap().clone();
store.data.as_s3().list_objects(
store,
store.clone(),
&global.js_thread_of_caller(callframe),
object_keys,
options,
Expand Down
132 changes: 59 additions & 73 deletions src/runtime/webcore/S3File.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ pub(crate) fn presign(global: &JSGlobalObject, callframe: &CallFrame) -> JsResul

let error_message = "Expected a S3 or path to presign";
let path_or_blob = parse_s3_path_or_blob(global, &mut args, error_message)?;
let (mut blob, options) = resolve_s3_blob(global, &mut args, path_or_blob, error_message)?;
get_presign_url_from(&mut blob, global, options)
let (blob, options) = resolve_s3_blob(global, &mut args, path_or_blob, error_message)?;
get_presign_url_from(&blob, global, options)
}

#[bun_jsc::host_fn]
Expand All @@ -173,11 +173,12 @@ pub(crate) fn unlink(global: &JSGlobalObject, callframe: &CallFrame) -> JsResult
let error_message = "Expected a S3 or path to delete";
let path_or_blob = parse_s3_path_or_blob(global, &mut args, error_message)?;
let (blob, options) = resolve_s3_blob(global, &mut args, path_or_blob, error_message)?;
let store = blob.store.get().as_ref().unwrap();
store
.data
.as_s3()
.unlink(store, &global.js_thread_of_caller(callframe), options)
let store = blob.store.get().as_ref().unwrap().clone();
store.data.as_s3().unlink(
store.clone(),
&global.js_thread_of_caller(callframe),
options,
)
}

#[bun_jsc::host_fn]
Expand Down Expand Up @@ -374,7 +375,7 @@ impl S3BlobStatTask {
result: s3::S3StatResult,
this: *mut core::ffi::c_void,
) -> JsResult<()> {
// SAFETY: `this` was allocated via heap::alloc in `exists`; reconstructing here replaces `defer this.deinit()`
// SAFETY: `this` is the box `start` leaked with heap::into_raw; reconstructing here replaces `defer this.deinit()`
let mut this = unsafe { bun_core::heap::take(this.cast::<S3BlobStatTask>()) };
// Copy the BackRef out so `this` is not borrowed across `&mut this.promise`.
let global_ref = this.global;
Expand Down Expand Up @@ -405,7 +406,7 @@ impl S3BlobStatTask {
}

fn on_s3_size_resolved(result: s3::S3StatResult, this: *mut core::ffi::c_void) -> JsResult<()> {
// SAFETY: `this` was allocated via heap::alloc in `size`; reconstructing here replaces `defer this.deinit()`
// SAFETY: `this` is the box `start` leaked with heap::into_raw; reconstructing here replaces `defer this.deinit()`
let mut this = unsafe { bun_core::heap::take(this.cast::<S3BlobStatTask>()) };
// Copy the BackRef out so `this` is not borrowed across `&mut this.promise`.
let global_ref = this.global;
Expand All @@ -430,7 +431,7 @@ impl S3BlobStatTask {
}

fn on_s3_stat_resolved(result: s3::S3StatResult, this: *mut core::ffi::c_void) -> JsResult<()> {
// SAFETY: `this` was allocated via heap::alloc in `stat`; reconstructing here replaces `defer this.deinit()`
// SAFETY: `this` is the box `start` leaked with heap::into_raw; reconstructing here replaces `defer this.deinit()`
let mut this = unsafe { bun_core::heap::take(this.cast::<S3BlobStatTask>()) };
// Copy the BackRef out so `this` is not borrowed across `&mut this.promise`.
let global_ref = this.global;
Expand Down Expand Up @@ -464,78 +465,71 @@ impl S3BlobStatTask {
Ok(())
}

pub(crate) fn exists(cx: &bun_jsc::JsThread<'_>, blob: &Blob) -> JsResult<JSValue> {
/// The HEAD request behind `exists()`, `size()` and `stat()`.
fn start(
cx: &bun_jsc::JsThread<'_>,
blob: &Blob,
verb: &str,
callback: fn(s3::S3StatResult, *mut core::ffi::c_void) -> JsResult<()>,
) -> JsResult<JSValue> {
let store = match s3_store(blob, cx.global(), verb) {
Ok(store) => store,
Err(err) => {
return Ok(bun_jsc::JSPromise::rejected_promise_with_caught_exception(
cx.global(),
err,
)?
.to_js());
}
};
let this = S3BlobStatTask::new(S3BlobStatTask {
promise: bun_jsc::JSPromiseStrong::init(cx.global()),
store: blob.store.get().as_ref().unwrap().clone(),
store,
global: bun_ptr::BackRef::new(cx.global()),
});
// SAFETY: `this` is a freshly leaked Box; sole pointer until handed to the s3
// callback below. Scoped shared access.
let promise = unsafe { (*this).promise.value() };
let s3_store = blob.store.get().as_ref().unwrap().data.as_s3();
let credentials = s3_store.get_credentials();
let path = s3_store.path();
let task = unsafe { &*this };
let (promise, s3_store) = (task.promise.value(), task.store.data.as_s3());
s3::stat(
credentials,
s3_store.get_credentials(),
cx.context(),
path,
S3BlobStatTask::on_s3_exists_resolved,
s3_store.path(),
callback,
this.cast::<core::ffi::c_void>(),
s3_store.request_payer,
)?;
Ok(promise)
}

pub(crate) fn exists(cx: &bun_jsc::JsThread<'_>, blob: &Blob) -> JsResult<JSValue> {
Self::start(cx, blob, "exists", S3BlobStatTask::on_s3_exists_resolved)
}

pub(crate) fn stat(cx: &bun_jsc::JsThread<'_>, blob: &Blob) -> JsResult<JSValue> {
let this = S3BlobStatTask::new(S3BlobStatTask {
promise: bun_jsc::JSPromiseStrong::init(cx.global()),
store: blob.store.get().as_ref().unwrap().clone(),
global: bun_ptr::BackRef::new(cx.global()),
});
// SAFETY: `this` is a freshly leaked Box; sole pointer until handed to the s3
// callback below. Scoped shared access.
let promise = unsafe { (*this).promise.value() };
let s3_store = blob.store.get().as_ref().unwrap().data.as_s3();
let credentials = s3_store.get_credentials();
let path = s3_store.path();
s3::stat(
credentials,
cx.context(),
path,
S3BlobStatTask::on_s3_stat_resolved,
this.cast::<core::ffi::c_void>(),
s3_store.request_payer,
)?;
Ok(promise)
Self::start(cx, blob, "stat", S3BlobStatTask::on_s3_stat_resolved)
}

pub(crate) fn size(cx: &bun_jsc::JsThread<'_>, blob: &mut Blob) -> JsResult<JSValue> {
let this = S3BlobStatTask::new(S3BlobStatTask {
promise: bun_jsc::JSPromiseStrong::init(cx.global()),
store: blob.store.get().as_ref().unwrap().clone(),
global: bun_ptr::BackRef::new(cx.global()),
});
// SAFETY: `this` is a freshly leaked Box; sole pointer until handed to the s3
// callback below. Scoped shared access.
let promise = unsafe { (*this).promise.value() };
let s3_store = blob.store.get().as_ref().unwrap().data.as_s3();
let credentials = s3_store.get_credentials();
let path = s3_store.path();
s3::stat(
credentials,
cx.context(),
path,
S3BlobStatTask::on_s3_size_resolved,
this.cast::<core::ffi::c_void>(),
s3_store.request_payer,
)?;
Ok(promise)
Self::start(cx, blob, "size", S3BlobStatTask::on_s3_size_resolved)
}

// Teardown (store deref, promise deinit, freeing the box) is handled by Box<Self> Drop.
}

/// Owned: the option getters that run next are user JS, which can take the store out of `blob`.
fn s3_store(blob: &Blob, global: &JSGlobalObject, verb: &str) -> JsResult<RefPtr<Store>> {
match blob.store().cloned() {
Some(store) if matches!(store.data, blob::store::Data::S3(_)) => Ok(store),
_ => Err(global
.err(
bun_jsc::ErrorCode::INVALID_THIS,
format_args!("{verb} is only possible for s3:// files"),
)
.throw()),
}
}

// `Method.fromJS` lives in `bun_http_jsc` so `bun_http_types` stays
// JSC-free. Thin local alias for the `getPresignUrlFrom` body.
#[inline]
Expand All @@ -544,23 +538,15 @@ fn method_from_js(global: &JSGlobalObject, value: JSValue) -> JsResult<Option<Me
}

pub(crate) fn get_presign_url_from(
this: &mut Blob,
this: &Blob,
global: &JSGlobalObject,
extra_options: Option<JSValue>,
) -> JsResult<JSValue> {
if !this.is_s3() {
return Err(global
.err(
bun_jsc::ErrorCode::INVALID_THIS,
format_args!("presign is only possible for s3:// files"),
)
.throw());
}

let mut method: Method = Method::GET;
let mut expires: usize = 86400; // 1 day default

let s3 = this.store.get().as_ref().unwrap().data.as_s3();
let store = s3_store(this, global, "presign")?;
Comment thread
robobun marked this conversation as resolved.
Resolved
let s3 = store.data.as_s3();
// `acl`/`storage_class`/`content_*` deliberately stay at their `None`
// defaults here — they are only seeded from the store when extra_options
// is provided (via `getCredentialsWithOptions` below).
Expand Down Expand Up @@ -657,7 +643,7 @@ fn get_bucket(this: &Blob, global: &JSGlobalObject) -> JsResult<JSValue> {
}

fn get_presign_url(
this: &mut Blob,
this: &Blob,
global: &JSGlobalObject,
callframe: &CallFrame,
) -> JsResult<JSValue> {
Expand Down Expand Up @@ -730,7 +716,7 @@ pub(crate) mod exports {
callframe: *mut CallFrame,
) -> JSValue {
// SAFETY: JSC method shim passes live `m_ctx`/global/callframe.
let (this, global, callframe) = unsafe { (&mut *this, &*global, &*callframe) };
let (this, global, callframe) = unsafe { (&*this, &*global, &*callframe) };
bun_jsc::to_js_host_call(global, || super::get_presign_url(this, global, callframe))
}

Expand All @@ -742,7 +728,7 @@ pub(crate) mod exports {
callframe: *mut CallFrame,
) -> JSValue {
// SAFETY: JSC method shim passes live `m_ctx`/global/callframe.
let (this, global, callframe) = unsafe { (&mut *this, &*global, &*callframe) };
let (this, global, callframe) = unsafe { (&*this, &*global, &*callframe) };
bun_jsc::to_js_host_call(global, || super::get_stat(this, global, callframe))
}
}
Expand Down
13 changes: 7 additions & 6 deletions src/runtime/webcore/blob/Store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,17 @@ pub(crate) trait S3Ext {
global_object: &JSGlobalObject,
) -> JsResult<S3CredentialsWithOptions>;
/// `store` is the heap `Store` that owns `self` (`self == &store.data.S3`).
/// Owned: the options are read by user JS, which can empty the Blob a borrow points into.
fn unlink(
&self,
store: &RefPtr<Store>,
store: RefPtr<Store>,
cx: &bun_jsc::JsThread<'_>,
extra_options: Option<JSValue>,
) -> JsResult<JSValue>;
/// See `unlink`.
fn list_objects(
&self,
store: &RefPtr<Store>,
store: RefPtr<Store>,
cx: &bun_jsc::JsThread<'_>,
list_options: JSValue,
extra_options: Option<JSValue>,
Expand Down Expand Up @@ -270,7 +271,7 @@ impl S3Ext for S3 {

fn unlink(
&self,
store: &RefPtr<Store>,
store: RefPtr<Store>,
cx: &bun_jsc::JsThread<'_>,
extra_options: Option<JSValue>,
) -> JsResult<JSValue> {
Expand Down Expand Up @@ -324,7 +325,7 @@ impl S3Ext for S3 {
Wrapper::resolve,
bun_core::heap::into_raw(Wrapper::new(Wrapper {
promise,
store: store.clone(),
store,
global: bun_ptr::BackRef::new(cx.global()),
}))
.cast::<c_void>(),
Expand All @@ -336,7 +337,7 @@ impl S3Ext for S3 {

fn list_objects(
&self,
store: &RefPtr<Store>,
store: RefPtr<Store>,
cx: &bun_jsc::JsThread<'_>,
list_options: JSValue,
extra_options: Option<JSValue>,
Expand Down Expand Up @@ -407,7 +408,7 @@ impl S3Ext for S3 {
// `Drop` after the async callback.
let wrapper = bun_core::heap::into_raw(Box::new(Wrapper {
promise,
store: store.clone(),
store,
resolved_list_options: options,
global: bun_ptr::BackRef::new(cx.global()),
}));
Expand Down
Loading
Loading