protocol.rs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use std::{borrow::Cow, sync::Arc};
  5. use crate::{
  6. manager::AppManager,
  7. webview::{InvokeRequest, UriSchemeProtocolHandler},
  8. Runtime,
  9. };
  10. use http::{
  11. header::{
  12. ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS,
  13. CONTENT_TYPE,
  14. },
  15. HeaderValue, Method, Request, StatusCode,
  16. };
  17. use url::Url;
  18. use super::{CallbackFn, InvokeBody, InvokeResponse};
  19. const TAURI_CALLBACK_HEADER_NAME: &str = "Tauri-Callback";
  20. const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error";
  21. const TAURI_INVOKE_KEY_HEADER_NAME: &str = "Tauri-Invoke-Key";
  22. const TAURI_RESPONSE_HEADER_NAME: &str = "Tauri-Response";
  23. const TAURI_RESPONSE_HEADER_ERROR: &str = "error";
  24. const TAURI_RESPONSE_HEADER_OK: &str = "ok";
  25. pub fn message_handler<R: Runtime>(
  26. manager: Arc<AppManager<R>>,
  27. ) -> crate::runtime::webview::WebviewIpcHandler<crate::EventLoopMessage, R> {
  28. Box::new(move |webview, request| handle_ipc_message(request, &manager, &webview.label))
  29. }
  30. pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeProtocolHandler {
  31. Box::new(move |request, responder| {
  32. #[cfg(feature = "tracing")]
  33. let span = tracing::trace_span!(
  34. "ipc::request",
  35. kind = "custom-protocol",
  36. request = tracing::field::Empty
  37. )
  38. .entered();
  39. let manager = manager.clone();
  40. let label = label.clone();
  41. let respond = move |mut response: http::Response<Cow<'static, [u8]>>| {
  42. response
  43. .headers_mut()
  44. .insert(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*"));
  45. response.headers_mut().insert(
  46. ACCESS_CONTROL_EXPOSE_HEADERS,
  47. HeaderValue::from_static(TAURI_RESPONSE_HEADER_NAME),
  48. );
  49. responder.respond(response);
  50. };
  51. match *request.method() {
  52. Method::POST => {
  53. if let Some(webview) = manager.get_webview(&label) {
  54. match parse_invoke_request(&manager, request) {
  55. Ok(request) => {
  56. #[cfg(feature = "tracing")]
  57. span.record(
  58. "request",
  59. match &request.body {
  60. InvokeBody::Json(j) => serde_json::to_string(j).unwrap(),
  61. InvokeBody::Raw(b) => serde_json::to_string(b).unwrap(),
  62. },
  63. );
  64. #[cfg(feature = "tracing")]
  65. let request_span = tracing::trace_span!("ipc::request::handle", cmd = request.cmd);
  66. webview.on_message(
  67. request,
  68. Box::new(move |_webview, _cmd, response, _callback, _error| {
  69. #[cfg(feature = "tracing")]
  70. let _respond_span = tracing::trace_span!(
  71. parent: &request_span,
  72. "ipc::request::respond"
  73. )
  74. .entered();
  75. #[cfg(feature = "tracing")]
  76. let response_span = tracing::trace_span!(
  77. "ipc::request::response",
  78. response = serde_json::to_string(&response).unwrap(),
  79. mime_type = tracing::field::Empty
  80. )
  81. .entered();
  82. let response_header = match &response {
  83. InvokeResponse::Ok(_) => TAURI_RESPONSE_HEADER_OK,
  84. InvokeResponse::Err(_) => TAURI_RESPONSE_HEADER_ERROR,
  85. };
  86. let (mut response, mime_type) = match response {
  87. InvokeResponse::Ok(InvokeBody::Json(v)) => (
  88. http::Response::new(serde_json::to_vec(&v).unwrap().into()),
  89. mime::APPLICATION_JSON,
  90. ),
  91. InvokeResponse::Ok(InvokeBody::Raw(v)) => (
  92. http::Response::new(v.into()),
  93. mime::APPLICATION_OCTET_STREAM,
  94. ),
  95. InvokeResponse::Err(e) => (
  96. http::Response::new(serde_json::to_vec(&e.0).unwrap().into()),
  97. mime::APPLICATION_JSON,
  98. ),
  99. };
  100. response
  101. .headers_mut()
  102. .insert(TAURI_RESPONSE_HEADER_NAME, response_header.parse().unwrap());
  103. #[cfg(feature = "tracing")]
  104. response_span.record("mime_type", mime_type.essence_str());
  105. response.headers_mut().insert(
  106. CONTENT_TYPE,
  107. HeaderValue::from_str(mime_type.essence_str()).unwrap(),
  108. );
  109. respond(response);
  110. }),
  111. );
  112. }
  113. Err(e) => {
  114. respond(
  115. http::Response::builder()
  116. .status(StatusCode::INTERNAL_SERVER_ERROR)
  117. .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
  118. .body(e.as_bytes().to_vec().into())
  119. .unwrap(),
  120. );
  121. }
  122. }
  123. } else {
  124. respond(
  125. http::Response::builder()
  126. .status(StatusCode::INTERNAL_SERVER_ERROR)
  127. .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
  128. .body(
  129. "failed to acquire webview reference"
  130. .as_bytes()
  131. .to_vec()
  132. .into(),
  133. )
  134. .unwrap(),
  135. );
  136. }
  137. }
  138. Method::OPTIONS => {
  139. let mut r = http::Response::new(Vec::new().into());
  140. r.headers_mut()
  141. .insert(ACCESS_CONTROL_ALLOW_HEADERS, HeaderValue::from_static("*"));
  142. respond(r);
  143. }
  144. _ => {
  145. let mut r = http::Response::new(
  146. "only POST and OPTIONS are allowed"
  147. .as_bytes()
  148. .to_vec()
  149. .into(),
  150. );
  151. *r.status_mut() = StatusCode::METHOD_NOT_ALLOWED;
  152. r.headers_mut().insert(
  153. CONTENT_TYPE,
  154. HeaderValue::from_str(mime::TEXT_PLAIN.essence_str()).unwrap(),
  155. );
  156. respond(r);
  157. }
  158. }
  159. })
  160. }
  161. fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager<R>, label: &str) {
  162. if let Some(webview) = manager.get_webview(label) {
  163. #[cfg(feature = "tracing")]
  164. let _span = tracing::trace_span!(
  165. "ipc::request",
  166. kind = "post-message",
  167. uri = request.uri().to_string(),
  168. request = request.body()
  169. )
  170. .entered();
  171. use serde::{Deserialize, Deserializer};
  172. #[derive(Default)]
  173. pub(crate) struct HeaderMap(http::HeaderMap);
  174. impl<'de> Deserialize<'de> for HeaderMap {
  175. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  176. where
  177. D: Deserializer<'de>,
  178. {
  179. let map = std::collections::HashMap::<String, String>::deserialize(deserializer)?;
  180. let mut headers = http::HeaderMap::default();
  181. for (key, value) in map {
  182. if let (Ok(key), Ok(value)) = (
  183. http::header::HeaderName::from_bytes(key.as_bytes()),
  184. http::HeaderValue::from_str(&value),
  185. ) {
  186. headers.insert(key, value);
  187. } else {
  188. return Err(serde::de::Error::custom(format!(
  189. "invalid header `{key}` `{value}`"
  190. )));
  191. }
  192. }
  193. Ok(Self(headers))
  194. }
  195. }
  196. #[derive(Deserialize, Default)]
  197. #[serde(rename_all = "camelCase")]
  198. struct RequestOptions {
  199. #[serde(default)]
  200. headers: HeaderMap,
  201. #[serde(default)]
  202. custom_protocol_ipc_blocked: bool,
  203. }
  204. #[derive(Deserialize)]
  205. struct Message {
  206. cmd: String,
  207. callback: CallbackFn,
  208. error: CallbackFn,
  209. payload: serde_json::Value,
  210. options: Option<RequestOptions>,
  211. #[serde(rename = "__TAURI_INVOKE_KEY__")]
  212. invoke_key: String,
  213. }
  214. #[allow(unused_mut)]
  215. let mut invoke_message: Option<crate::Result<Message>> = None;
  216. #[cfg(feature = "isolation")]
  217. {
  218. #[derive(serde::Deserialize)]
  219. struct IsolationMessage<'a> {
  220. cmd: String,
  221. callback: CallbackFn,
  222. error: CallbackFn,
  223. payload: crate::utils::pattern::isolation::RawIsolationPayload<'a>,
  224. options: Option<RequestOptions>,
  225. #[serde(rename = "__TAURI_INVOKE_KEY__")]
  226. invoke_key: String,
  227. }
  228. if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern {
  229. #[cfg(feature = "tracing")]
  230. let _span = tracing::trace_span!("ipc::request::decrypt_isolation_payload").entered();
  231. invoke_message.replace(
  232. serde_json::from_str::<IsolationMessage<'_>>(request.body())
  233. .map_err(Into::into)
  234. .and_then(|message| {
  235. Ok(Message {
  236. cmd: message.cmd,
  237. callback: message.callback,
  238. error: message.error,
  239. payload: serde_json::from_slice(&crypto_keys.decrypt(message.payload)?)?,
  240. options: message.options,
  241. invoke_key: message.invoke_key,
  242. })
  243. }),
  244. );
  245. }
  246. }
  247. let message = invoke_message.unwrap_or_else(|| {
  248. #[cfg(feature = "tracing")]
  249. let _span = tracing::trace_span!("ipc::request::deserialize").entered();
  250. serde_json::from_str::<Message>(request.body()).map_err(Into::into)
  251. });
  252. match message {
  253. Ok(message) => {
  254. let options = message.options.unwrap_or_default();
  255. let request = InvokeRequest {
  256. cmd: message.cmd,
  257. callback: message.callback,
  258. error: message.error,
  259. url: Url::parse(&request.uri().to_string()).expect("invalid IPC request URL"),
  260. body: message.payload.into(),
  261. headers: options.headers.0,
  262. invoke_key: message.invoke_key,
  263. };
  264. #[cfg(feature = "tracing")]
  265. let request_span = tracing::trace_span!("ipc::request::handle", cmd = request.cmd);
  266. webview.on_message(
  267. request,
  268. Box::new(move |webview, cmd, response, callback, error| {
  269. use crate::ipc::{
  270. format_callback::{
  271. format as format_callback, format_result as format_callback_result,
  272. },
  273. Channel,
  274. };
  275. use crate::sealed::ManagerBase;
  276. use serde_json::Value as JsonValue;
  277. #[cfg(feature = "tracing")]
  278. let _respond_span = tracing::trace_span!(
  279. parent: &request_span,
  280. "ipc::request::respond"
  281. )
  282. .entered();
  283. // the channel data command is the only command that uses a custom protocol on Linux
  284. if webview.manager().webview.invoke_responder.is_none() {
  285. fn responder_eval<R: Runtime>(
  286. webview: &crate::Webview<R>,
  287. js: crate::Result<String>,
  288. error: CallbackFn,
  289. ) {
  290. let eval_js = match js {
  291. Ok(js) => js,
  292. Err(e) => format_callback(error, &e.to_string())
  293. .expect("unable to serialize response error string to json"),
  294. };
  295. let _ = webview.eval(&eval_js);
  296. }
  297. let can_use_channel_for_response = cmd
  298. != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND
  299. && !options.custom_protocol_ipc_blocked;
  300. #[cfg(feature = "tracing")]
  301. let _response_span = tracing::trace_span!(
  302. "ipc::request::response",
  303. response = serde_json::to_string(&response).unwrap(),
  304. mime_type = match &response {
  305. InvokeResponse::Ok(InvokeBody::Json(_)) => mime::APPLICATION_JSON,
  306. InvokeResponse::Ok(InvokeBody::Raw(_)) => mime::APPLICATION_OCTET_STREAM,
  307. InvokeResponse::Err(_) => mime::APPLICATION_JSON,
  308. }
  309. .essence_str()
  310. )
  311. .entered();
  312. match &response {
  313. InvokeResponse::Ok(InvokeBody::Json(v)) => {
  314. if !(cfg!(target_os = "macos") || cfg!(target_os = "ios"))
  315. && matches!(v, JsonValue::Object(_) | JsonValue::Array(_))
  316. && can_use_channel_for_response
  317. {
  318. let _ = Channel::from_callback_fn(webview, callback).send(v);
  319. } else {
  320. responder_eval(
  321. &webview,
  322. format_callback_result(Result::<_, ()>::Ok(v), callback, error),
  323. error,
  324. )
  325. }
  326. }
  327. InvokeResponse::Ok(InvokeBody::Raw(v)) => {
  328. if cfg!(target_os = "macos")
  329. || cfg!(target_os = "ios")
  330. || !can_use_channel_for_response
  331. {
  332. responder_eval(
  333. &webview,
  334. format_callback_result(Result::<_, ()>::Ok(v), callback, error),
  335. error,
  336. );
  337. } else {
  338. let _ =
  339. Channel::from_callback_fn(webview, callback).send(InvokeBody::Raw(v.clone()));
  340. }
  341. }
  342. InvokeResponse::Err(e) => responder_eval(
  343. &webview,
  344. format_callback_result(Result::<(), _>::Err(&e.0), callback, error),
  345. error,
  346. ),
  347. }
  348. }
  349. }),
  350. );
  351. }
  352. Err(e) => {
  353. #[cfg(feature = "tracing")]
  354. tracing::trace!("ipc.request.error {}", e);
  355. let _ = webview.eval(&format!(
  356. r#"console.error({})"#,
  357. serde_json::Value::String(e.to_string())
  358. ));
  359. }
  360. }
  361. }
  362. }
  363. fn parse_invoke_request<R: Runtime>(
  364. #[allow(unused_variables)] manager: &AppManager<R>,
  365. request: http::Request<Vec<u8>>,
  366. ) -> std::result::Result<InvokeRequest, String> {
  367. #[allow(unused_mut)]
  368. let (parts, mut body) = request.into_parts();
  369. // skip leading `/`
  370. let cmd = percent_encoding::percent_decode(parts.uri.path()[1..].as_bytes())
  371. .decode_utf8_lossy()
  372. .to_string();
  373. // on Android and on Linux (without the linux-ipc-protocol Cargo feature) we cannot read the request body
  374. // so we must ignore it because some commands use the IPC for faster response
  375. let has_payload = !body.is_empty();
  376. #[allow(unused_mut)]
  377. let mut content_type = parts
  378. .headers
  379. .get(http::header::CONTENT_TYPE)
  380. .and_then(|h| h.to_str().ok())
  381. .map(|mime| mime.parse())
  382. .unwrap_or(Ok(mime::APPLICATION_OCTET_STREAM))
  383. .map_err(|_| "unknown content type")?;
  384. #[cfg(feature = "isolation")]
  385. if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern {
  386. // if the platform does not support request body, we ignore it
  387. if has_payload {
  388. #[cfg(feature = "tracing")]
  389. let _span = tracing::trace_span!("ipc::request::decrypt_isolation_payload").entered();
  390. (body, content_type) = crate::utils::pattern::isolation::RawIsolationPayload::try_from(&body)
  391. .and_then(|raw| {
  392. let content_type = raw.content_type().clone();
  393. crypto_keys.decrypt(raw).map(|decrypted| {
  394. (
  395. decrypted,
  396. content_type
  397. .parse()
  398. .unwrap_or(mime::APPLICATION_OCTET_STREAM),
  399. )
  400. })
  401. })
  402. .map_err(|e| e.to_string())?;
  403. }
  404. }
  405. let invoke_key = parts
  406. .headers
  407. .get(TAURI_INVOKE_KEY_HEADER_NAME)
  408. .ok_or("missing Tauri-Invoke-Key header")?
  409. .to_str()
  410. .map_err(|_| "Tauri invoke key header value must be a string")?
  411. .to_owned();
  412. let url = Url::parse(
  413. parts
  414. .headers
  415. .get("Origin")
  416. .ok_or("missing Origin header")?
  417. .to_str()
  418. .map_err(|_| "Origin header value must be a string")?,
  419. )
  420. .map_err(|_| "Origin header is not a valid URL")?;
  421. let callback = CallbackFn(
  422. parts
  423. .headers
  424. .get(TAURI_CALLBACK_HEADER_NAME)
  425. .ok_or("missing Tauri-Callback header")?
  426. .to_str()
  427. .map_err(|_| "Tauri callback header value must be a string")?
  428. .parse()
  429. .map_err(|_| "Tauri callback header value must be a numeric string")?,
  430. );
  431. let error = CallbackFn(
  432. parts
  433. .headers
  434. .get(TAURI_ERROR_HEADER_NAME)
  435. .ok_or("missing Tauri-Error header")?
  436. .to_str()
  437. .map_err(|_| "Tauri error header value must be a string")?
  438. .parse()
  439. .map_err(|_| "Tauri error header value must be a numeric string")?,
  440. );
  441. #[cfg(feature = "tracing")]
  442. let span = tracing::trace_span!("ipc::request::deserialize").entered();
  443. let body = if content_type == mime::APPLICATION_OCTET_STREAM {
  444. body.into()
  445. } else if content_type == mime::APPLICATION_JSON {
  446. // if the platform does not support request body, we ignore it
  447. if has_payload {
  448. serde_json::from_slice::<serde_json::Value>(&body)
  449. .map_err(|e| e.to_string())?
  450. .into()
  451. } else {
  452. serde_json::Value::Object(Default::default()).into()
  453. }
  454. } else {
  455. return Err(format!("content type {content_type} is not implemented"));
  456. };
  457. #[cfg(feature = "tracing")]
  458. drop(span);
  459. let payload = InvokeRequest {
  460. cmd,
  461. callback,
  462. error,
  463. url,
  464. body,
  465. headers: parts.headers,
  466. invoke_key,
  467. };
  468. Ok(payload)
  469. }
  470. #[cfg(test)]
  471. mod tests {
  472. use std::str::FromStr;
  473. use super::*;
  474. use crate::{manager::AppManager, plugin::PluginStore, StateManager, Wry};
  475. use http::header::*;
  476. use serde_json::json;
  477. use tauri_macros::generate_context;
  478. #[test]
  479. fn parse_invoke_request() {
  480. let context = generate_context!("test/fixture/src-tauri/tauri.conf.json", crate, test = true);
  481. let manager: AppManager<Wry> = AppManager::with_handlers(
  482. context,
  483. PluginStore::default(),
  484. Box::new(|_| false),
  485. None,
  486. Default::default(),
  487. StateManager::new(),
  488. Default::default(),
  489. Default::default(),
  490. Default::default(),
  491. (None, "".into()),
  492. crate::generate_invoke_key().unwrap(),
  493. );
  494. let cmd = "write_something";
  495. let url = "tauri://localhost";
  496. let invoke_key = "1234ahdsjkl123";
  497. let callback = 12378123;
  498. let error = 6243;
  499. let headers = HeaderMap::from_iter(vec![
  500. (
  501. CONTENT_TYPE,
  502. HeaderValue::from_str(mime::APPLICATION_OCTET_STREAM.as_ref()).unwrap(),
  503. ),
  504. (
  505. HeaderName::from_str(TAURI_INVOKE_KEY_HEADER_NAME).unwrap(),
  506. HeaderValue::from_str(invoke_key).unwrap(),
  507. ),
  508. (
  509. HeaderName::from_str(TAURI_CALLBACK_HEADER_NAME).unwrap(),
  510. HeaderValue::from_str(&callback.to_string()).unwrap(),
  511. ),
  512. (
  513. HeaderName::from_str(TAURI_ERROR_HEADER_NAME).unwrap(),
  514. HeaderValue::from_str(&error.to_string()).unwrap(),
  515. ),
  516. (ORIGIN, HeaderValue::from_str("tauri://localhost").unwrap()),
  517. ]);
  518. let mut request = Request::builder().uri(format!("ipc://localhost/{cmd}"));
  519. *request.headers_mut().unwrap() = headers.clone();
  520. let body = vec![123, 31, 45];
  521. let request = request.body(body.clone()).unwrap();
  522. let invoke_request = super::parse_invoke_request(&manager, request).unwrap();
  523. assert_eq!(invoke_request.cmd, cmd);
  524. assert_eq!(invoke_request.callback.0, callback);
  525. assert_eq!(invoke_request.error.0, error);
  526. assert_eq!(invoke_request.invoke_key, invoke_key);
  527. assert_eq!(invoke_request.url, url.parse().unwrap());
  528. assert_eq!(invoke_request.headers, headers);
  529. assert_eq!(invoke_request.body, InvokeBody::Raw(body));
  530. let body = json!({
  531. "key": 1,
  532. "anotherKey": "asda",
  533. });
  534. let mut headers = headers.clone();
  535. headers.insert(
  536. CONTENT_TYPE,
  537. HeaderValue::from_str(mime::APPLICATION_JSON.as_ref()).unwrap(),
  538. );
  539. let mut request = Request::builder().uri(format!("ipc://localhost/{cmd}"));
  540. *request.headers_mut().unwrap() = headers.clone();
  541. let request = request.body(serde_json::to_vec(&body).unwrap()).unwrap();
  542. let invoke_request = super::parse_invoke_request(&manager, request).unwrap();
  543. assert_eq!(invoke_request.headers, headers);
  544. assert_eq!(invoke_request.body, InvokeBody::Json(body));
  545. }
  546. #[test]
  547. #[cfg(feature = "isolation")]
  548. fn parse_invoke_request_isolation() {
  549. let context = generate_context!(
  550. "test/fixture/isolation/src-tauri/tauri.conf.json",
  551. crate,
  552. test = false
  553. );
  554. let crate::pattern::Pattern::Isolation { crypto_keys, .. } = &context.pattern else {
  555. unreachable!()
  556. };
  557. let mut nonce = [0u8; 12];
  558. getrandom::getrandom(&mut nonce).unwrap();
  559. let body_raw = vec![1, 41, 65, 12, 78];
  560. let body_bytes = crypto_keys.aes_gcm().encrypt(&nonce, &body_raw).unwrap();
  561. let isolation_payload_raw = json!({
  562. "nonce": nonce,
  563. "payload": body_bytes,
  564. "contentType": mime::APPLICATION_OCTET_STREAM.to_string(),
  565. });
  566. let body_json = json!({
  567. "key": 1,
  568. "anotherKey": "string"
  569. });
  570. let body_bytes = crypto_keys
  571. .aes_gcm()
  572. .encrypt(&nonce, &serde_json::to_vec(&body_json).unwrap())
  573. .unwrap();
  574. let isolation_payload_json = json!({
  575. "nonce": nonce,
  576. "payload": body_bytes,
  577. "contentType": mime::APPLICATION_JSON.to_string(),
  578. });
  579. let manager: AppManager<Wry> = AppManager::with_handlers(
  580. context,
  581. PluginStore::default(),
  582. Box::new(|_| false),
  583. None,
  584. Default::default(),
  585. StateManager::new(),
  586. Default::default(),
  587. Default::default(),
  588. Default::default(),
  589. (None, "".into()),
  590. crate::generate_invoke_key().unwrap(),
  591. );
  592. let cmd = "write_something";
  593. let url = "tauri://localhost";
  594. let invoke_key = "1234ahdsjkl123";
  595. let callback = 12378123;
  596. let error = 6243;
  597. let headers = HeaderMap::from_iter(vec![
  598. (
  599. CONTENT_TYPE,
  600. HeaderValue::from_str(mime::APPLICATION_JSON.as_ref()).unwrap(),
  601. ),
  602. (
  603. HeaderName::from_str(TAURI_INVOKE_KEY_HEADER_NAME).unwrap(),
  604. HeaderValue::from_str(invoke_key).unwrap(),
  605. ),
  606. (
  607. HeaderName::from_str(TAURI_CALLBACK_HEADER_NAME).unwrap(),
  608. HeaderValue::from_str(&callback.to_string()).unwrap(),
  609. ),
  610. (
  611. HeaderName::from_str(TAURI_ERROR_HEADER_NAME).unwrap(),
  612. HeaderValue::from_str(&error.to_string()).unwrap(),
  613. ),
  614. (ORIGIN, HeaderValue::from_str("tauri://localhost").unwrap()),
  615. ]);
  616. let mut request = Request::builder().uri(format!("ipc://localhost/{cmd}"));
  617. *request.headers_mut().unwrap() = headers.clone();
  618. let body = serde_json::to_vec(&isolation_payload_raw).unwrap();
  619. let request = request.body(body).unwrap();
  620. let invoke_request = super::parse_invoke_request(&manager, request).unwrap();
  621. assert_eq!(invoke_request.cmd, cmd);
  622. assert_eq!(invoke_request.callback.0, callback);
  623. assert_eq!(invoke_request.error.0, error);
  624. assert_eq!(invoke_request.invoke_key, invoke_key);
  625. assert_eq!(invoke_request.url, url.parse().unwrap());
  626. assert_eq!(invoke_request.headers, headers);
  627. assert_eq!(invoke_request.body, InvokeBody::Raw(body_raw));
  628. let mut request = Request::builder().uri(format!("ipc://localhost/{cmd}"));
  629. *request.headers_mut().unwrap() = headers.clone();
  630. let body = serde_json::to_vec(&isolation_payload_json).unwrap();
  631. let request = request.body(body).unwrap();
  632. let invoke_request = super::parse_invoke_request(&manager, request).unwrap();
  633. assert_eq!(invoke_request.headers, headers);
  634. assert_eq!(invoke_request.body, InvokeBody::Json(body_json));
  635. }
  636. }