/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
usecrate::{backend::Backend, settings::GLOBAL_SETTINGS}; usecrate::{msg_types, Error}; use ffi_support::{ByteBuffer, FfiStr};
impl From<crate::Request> for msg_types::Request { fn from(request: crate::Request) -> Self { let settings = GLOBAL_SETTINGS.read();
msg_types::Request {
url: request.url.to_string(),
body: request.body, // Real weird that this needs to be specified as an i32, but // it certainly makes it convenient for us...
method: request.method as i32,
headers: request.headers.into(),
follow_redirects: settings.follow_redirects,
use_caches: settings.use_caches,
connect_timeout_secs: settings.connect_timeout.map_or(0, |d| d.as_secs() as i32),
read_timeout_secs: settings.read_timeout.map_or(0, |d| d.as_secs() as i32),
}
}
}
pubstruct FfiBackend; impl Backend for FfiBackend { fn send(&self, request: crate::Request) -> Result<crate::Response, Error> { use ffi_support::IntoFfi; use prost::Message; super::note_backend("FFI (trusted)");
let method = request.method; let fetch = callback_holder::get_callback().ok_or(Error::BackendNotInitialized)?; let proto_req: msg_types::Request = request.into(); let buf = proto_req.into_ffi_value(); let response = unsafe { fetch(buf) }; // This way we'll Drop it if we panic, unlike if we just got a slice into // it. Besides, we already own it. let response_bytes = response.destroy_into_vec();
let response: msg_types::Response = match Message::decode(response_bytes.as_slice()) {
Ok(v) => v,
Err(e) => {
panic!( "Failed to parse protobuf returned from fetch callback! {}",
e
);
}
};
iflet Some(exn) = response.exception_message { return Err(Error::NetworkError(format!("Java error: {:?}", exn)));
} let status = response
.status
.ok_or_else(|| backend_error!("Missing HTTP status"))?;
if status < 0 || status > i32::from(u16::MAX) { return Err(backend_error!("Illegal HTTP status: {}", status));
}
letmut headers = crate::Headers::with_capacity(response.headers.len()); for (name, val) in response.headers { let hname = matchcrate::HeaderName::new(name) {
Ok(name) => name,
Err(e) => { // Ignore headers with invalid names, since nobody can look for them anyway.
log::warn!("Server sent back invalid header name: '{}'", e); continue;
}
}; // Not using Header::new since the error it returns is for request headers.
headers.insert_header(crate::Header::new_unchecked(hname, val));
}
let url = url::Url::parse(
&response
.url
.ok_or_else(|| backend_error!("Response has no URL"))?,
)
.map_err(|e| backend_error!("Response has illegal URL: {}", e))?;
Ok(crate::Response {
url,
request_method: method,
body: response.body.unwrap_or_default(),
status: status as u16,
headers,
})
}
}
/// Type of the callback we need callers on the other side of the FFI to /// provide. /// /// Takes and returns a ffi_support::ByteBuffer. (TODO: it would be nice if we could /// make this take/return pointers, so that we could use JNA direct mapping. Maybe /// we need some kind of ThinBuffer?) /// /// This is a bit weird, since it requires us to allow code on the other side of /// the FFI to allocate a ByteBuffer from us, but it works. /// /// The code on the other side of the FFI is responsible for freeing the ByteBuffer /// it's passed using `viaduct_destroy_bytebuffer`. type FetchCallback = unsafeextern"C"fn(ByteBuffer) -> ByteBuffer;
/// Module that manages get/set of the global fetch callback pointer. mod callback_holder { usesuper::FetchCallback; use std::sync::atomic::{AtomicUsize, Ordering};
/// Note: We only assign to this once. static CALLBACK_PTR: AtomicUsize = AtomicUsize::new(0);
// Overly-paranoid sanity checking to ensure that these types are // convertible between each-other. `transmute` actually should check this for // us too, but this helps document the invariants we rely on in this code. // // Note that these are guaranteed by // https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html // and thus this is a little paranoid.
ffi_support::static_assert!(
STATIC_ASSERT_USIZE_EQ_FUNC_SIZE,
std::mem::size_of::<usize>() == std::mem::size_of::<FetchCallback>()
);
/// Get the function pointer to the FetchCallback. Panics if the callback /// has not yet been initialized. pub(super) fn get_callback() -> Option<FetchCallback> { let ptr_value = CALLBACK_PTR.load(Ordering::SeqCst); unsafe { std::mem::transmute::<usize, Option<FetchCallback>>(ptr_value) }
}
/// Set the function pointer to the FetchCallback. Returns false if we did nothing because the callback had already been initialized pub(super) fn set_callback(h: FetchCallback) -> bool { let as_usize = h as usize; match CALLBACK_PTR.compare_exchange(0, as_usize, Ordering::SeqCst, Ordering::SeqCst) {
Ok(_) => true,
Err(_) => { // This is an internal bug, the other side of the FFI should ensure // it sets this only once. Note that this is actually going to be // before logging is initialized in practice, so there's not a lot // we can actually do here.
log::error!("Bug: Initialized CALLBACK_PTR multiple times"); false
}
}
}
}
/// Return a ByteBuffer of the requested size. This is used to store the /// response from the callback. #[no_mangle] pubextern"C"fn viaduct_alloc_bytebuffer(sz: i32) -> ByteBuffer { letmut error = ffi_support::ExternError::default(); let buffer =
ffi_support::call_with_output(&mut error, || ByteBuffer::new_with_size(sz.max(0) as usize));
error.consume_and_log_if_error();
buffer
}
/// Allows connections to the hard-coded address the Android Emulator uses for /// localhost. It would be easy to support allowing the address to be passed in, /// but we've made a decision to avoid that possible footgun. The expectation is /// that this will only be called in debug builds or if the app can determine it /// is in the emulator, but the Rust code doesn't know that, so we can't check. #[no_mangle] pubextern"C"fn viaduct_allow_android_emulator_loopback() { letmut error = ffi_support::ExternError::default();
ffi_support::call_with_output(&mut error, || { let url = url::Url::parse("http://10.0.2.2").unwrap(); letmut settings = GLOBAL_SETTINGS.write();
settings.addn_allowed_insecure_url = Some(url);
});
error.consume_and_log_if_error();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.