protocol.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. // Copyright 2019-2023 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use std::borrow::Cow;
  5. use http::{
  6. header::{ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE},
  7. HeaderValue, Method, Request as HttpRequest, Response as HttpResponse, StatusCode,
  8. };
  9. use crate::{
  10. manager::WindowManager,
  11. window::{InvokeRequest, UriSchemeProtocolHandler},
  12. Runtime,
  13. };
  14. use super::{CallbackFn, InvokeBody, InvokeResponse};
  15. const TAURI_CALLBACK_HEADER_NAME: &str = "Tauri-Callback";
  16. const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error";
  17. #[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))]
  18. pub fn message_handler<R: Runtime>(
  19. manager: WindowManager<R>,
  20. ) -> crate::runtime::webview::WebviewIpcHandler<crate::EventLoopMessage, R> {
  21. Box::new(move |window, request| handle_ipc_message(request, &manager, &window.label))
  22. }
  23. pub fn get<R: Runtime>(manager: WindowManager<R>, label: String) -> UriSchemeProtocolHandler {
  24. Box::new(move |request, responder| {
  25. let manager = manager.clone();
  26. let label = label.clone();
  27. let respond = move |mut response: http::Response<Cow<'static, [u8]>>| {
  28. response
  29. .headers_mut()
  30. .insert(ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*"));
  31. responder.respond(response);
  32. };
  33. match *request.method() {
  34. Method::POST => {
  35. if let Some(window) = manager.get_window(&label) {
  36. match parse_invoke_request(&manager, request) {
  37. Ok(request) => {
  38. window.on_message(
  39. request,
  40. Box::new(move |_window, _cmd, response, _callback, _error| {
  41. let (mut response, mime_type) = match response {
  42. InvokeResponse::Ok(InvokeBody::Json(v)) => (
  43. HttpResponse::new(serde_json::to_vec(&v).unwrap().into()),
  44. mime::APPLICATION_JSON,
  45. ),
  46. InvokeResponse::Ok(InvokeBody::Raw(v)) => {
  47. (HttpResponse::new(v.into()), mime::APPLICATION_OCTET_STREAM)
  48. }
  49. InvokeResponse::Err(e) => {
  50. let mut response =
  51. HttpResponse::new(serde_json::to_vec(&e.0).unwrap().into());
  52. *response.status_mut() = StatusCode::BAD_REQUEST;
  53. (response, mime::TEXT_PLAIN)
  54. }
  55. };
  56. response.headers_mut().insert(
  57. CONTENT_TYPE,
  58. HeaderValue::from_str(mime_type.essence_str()).unwrap(),
  59. );
  60. respond(response);
  61. }),
  62. );
  63. }
  64. Err(e) => {
  65. respond(
  66. HttpResponse::builder()
  67. .status(StatusCode::BAD_REQUEST)
  68. .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
  69. .body(e.as_bytes().to_vec().into())
  70. .unwrap(),
  71. );
  72. }
  73. }
  74. } else {
  75. respond(
  76. HttpResponse::builder()
  77. .status(StatusCode::BAD_REQUEST)
  78. .header(CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
  79. .body(
  80. "failed to acquire window reference"
  81. .as_bytes()
  82. .to_vec()
  83. .into(),
  84. )
  85. .unwrap(),
  86. );
  87. }
  88. }
  89. Method::OPTIONS => {
  90. let mut r = HttpResponse::new(Vec::new().into());
  91. r.headers_mut().insert(
  92. ACCESS_CONTROL_ALLOW_HEADERS,
  93. HeaderValue::from_static("Content-Type, Tauri-Callback, Tauri-Error, Tauri-Channel-Id"),
  94. );
  95. respond(r);
  96. }
  97. _ => {
  98. let mut r = HttpResponse::new(
  99. "only POST and OPTIONS are allowed"
  100. .as_bytes()
  101. .to_vec()
  102. .into(),
  103. );
  104. *r.status_mut() = StatusCode::METHOD_NOT_ALLOWED;
  105. r.headers_mut().insert(
  106. CONTENT_TYPE,
  107. HeaderValue::from_str(mime::TEXT_PLAIN.essence_str()).unwrap(),
  108. );
  109. respond(r);
  110. }
  111. }
  112. })
  113. }
  114. #[cfg(any(target_os = "macos", target_os = "ios", not(ipc_custom_protocol)))]
  115. fn handle_ipc_message<R: Runtime>(message: String, manager: &WindowManager<R>, label: &str) {
  116. if let Some(window) = manager.get_window(label) {
  117. use serde::{Deserialize, Deserializer};
  118. pub(crate) struct HeaderMap(http::HeaderMap);
  119. impl<'de> Deserialize<'de> for HeaderMap {
  120. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  121. where
  122. D: Deserializer<'de>,
  123. {
  124. let map = std::collections::HashMap::<String, String>::deserialize(deserializer)?;
  125. let mut headers = http::HeaderMap::default();
  126. for (key, value) in map {
  127. if let (Ok(key), Ok(value)) = (
  128. http::HeaderName::from_bytes(key.as_bytes()),
  129. http::HeaderValue::from_str(&value),
  130. ) {
  131. headers.insert(key, value);
  132. } else {
  133. return Err(serde::de::Error::custom(format!(
  134. "invalid header `{key}` `{value}`"
  135. )));
  136. }
  137. }
  138. Ok(Self(headers))
  139. }
  140. }
  141. #[derive(Deserialize)]
  142. struct RequestOptions {
  143. headers: HeaderMap,
  144. }
  145. #[derive(Deserialize)]
  146. struct Message {
  147. cmd: String,
  148. callback: CallbackFn,
  149. error: CallbackFn,
  150. payload: serde_json::Value,
  151. options: Option<RequestOptions>,
  152. }
  153. #[allow(unused_mut)]
  154. let mut invoke_message: Option<crate::Result<Message>> = None;
  155. #[cfg(feature = "isolation")]
  156. {
  157. #[derive(serde::Deserialize)]
  158. struct IsolationMessage<'a> {
  159. cmd: String,
  160. callback: CallbackFn,
  161. error: CallbackFn,
  162. payload: crate::utils::pattern::isolation::RawIsolationPayload<'a>,
  163. options: Option<RequestOptions>,
  164. }
  165. if let crate::Pattern::Isolation { crypto_keys, .. } = manager.pattern() {
  166. invoke_message.replace(
  167. serde_json::from_str::<IsolationMessage<'_>>(&message)
  168. .map_err(Into::into)
  169. .and_then(|message| {
  170. Ok(Message {
  171. cmd: message.cmd,
  172. callback: message.callback,
  173. error: message.error,
  174. payload: serde_json::from_slice(&crypto_keys.decrypt(message.payload)?)?,
  175. options: message.options,
  176. })
  177. }),
  178. );
  179. }
  180. }
  181. match invoke_message
  182. .unwrap_or_else(|| serde_json::from_str::<Message>(&message).map_err(Into::into))
  183. {
  184. Ok(message) => {
  185. window.on_message(
  186. InvokeRequest {
  187. cmd: message.cmd,
  188. callback: message.callback,
  189. error: message.error,
  190. body: message.payload.into(),
  191. headers: message.options.map(|o| o.headers.0).unwrap_or_default(),
  192. },
  193. Box::new(move |window, cmd, response, callback, error| {
  194. use crate::ipc::{
  195. format_callback::{
  196. format as format_callback, format_result as format_callback_result,
  197. },
  198. Channel,
  199. };
  200. use serde_json::Value as JsonValue;
  201. // the channel data command is the only command that uses a custom protocol on Linux
  202. if window.manager.invoke_responder().is_none()
  203. && cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND
  204. {
  205. fn responder_eval<R: Runtime>(
  206. window: &crate::Window<R>,
  207. js: crate::api::Result<String>,
  208. error: CallbackFn,
  209. ) {
  210. let eval_js = match js {
  211. Ok(js) => js,
  212. Err(e) => format_callback(error, &e.to_string())
  213. .expect("unable to serialize response error string to json"),
  214. };
  215. let _ = window.eval(&eval_js);
  216. }
  217. match &response {
  218. InvokeResponse::Ok(InvokeBody::Json(v)) => {
  219. if !(cfg!(target_os = "macos") || cfg!(target_os = "ios"))
  220. && matches!(v, JsonValue::Object(_) | JsonValue::Array(_))
  221. {
  222. let _ = Channel::from_ipc(window.clone(), callback).send(v);
  223. } else {
  224. responder_eval(
  225. &window,
  226. format_callback_result(Result::<_, ()>::Ok(v), callback, error),
  227. error,
  228. )
  229. }
  230. }
  231. InvokeResponse::Ok(InvokeBody::Raw(v)) => {
  232. if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
  233. responder_eval(
  234. &window,
  235. format_callback_result(Result::<_, ()>::Ok(v), callback, error),
  236. error,
  237. );
  238. } else {
  239. let _ =
  240. Channel::from_ipc(window.clone(), callback).send(InvokeBody::Raw(v.clone()));
  241. }
  242. }
  243. InvokeResponse::Err(e) => responder_eval(
  244. &window,
  245. format_callback_result(Result::<(), _>::Err(&e.0), callback, error),
  246. error,
  247. ),
  248. }
  249. }
  250. }),
  251. );
  252. }
  253. Err(e) => {
  254. let _ = window.eval(&format!(
  255. r#"console.error({})"#,
  256. serde_json::Value::String(e.to_string())
  257. ));
  258. }
  259. }
  260. }
  261. }
  262. fn parse_invoke_request<R: Runtime>(
  263. #[allow(unused_variables)] manager: &WindowManager<R>,
  264. request: HttpRequest<Vec<u8>>,
  265. ) -> std::result::Result<InvokeRequest, String> {
  266. #[allow(unused_mut)]
  267. let (parts, mut body) = request.into_parts();
  268. let cmd = parts.uri.path().trim_start_matches('/');
  269. let cmd = percent_encoding::percent_decode(cmd.as_bytes())
  270. .decode_utf8_lossy()
  271. .to_string();
  272. // the body is not set if ipc_custom_protocol is not enabled so we'll just ignore it
  273. #[cfg(all(feature = "isolation", ipc_custom_protocol))]
  274. if let crate::Pattern::Isolation { crypto_keys, .. } = manager.pattern() {
  275. body = crate::utils::pattern::isolation::RawIsolationPayload::try_from(&body)
  276. .and_then(|raw| crypto_keys.decrypt(raw))
  277. .map_err(|e| e.to_string())?;
  278. }
  279. let callback = CallbackFn(
  280. parts
  281. .headers
  282. .get(TAURI_CALLBACK_HEADER_NAME)
  283. .ok_or("missing Tauri-Callback header")?
  284. .to_str()
  285. .map_err(|_| "Tauri callback header value must be a string")?
  286. .parse()
  287. .map_err(|_| "Tauri callback header value must be a numeric string")?,
  288. );
  289. let error = CallbackFn(
  290. parts
  291. .headers
  292. .get(TAURI_ERROR_HEADER_NAME)
  293. .ok_or("missing Tauri-Error header")?
  294. .to_str()
  295. .map_err(|_| "Tauri error header value must be a string")?
  296. .parse()
  297. .map_err(|_| "Tauri error header value must be a numeric string")?,
  298. );
  299. let content_type = parts
  300. .headers
  301. .get(reqwest::header::CONTENT_TYPE)
  302. .and_then(|h| h.to_str().ok())
  303. .map(|mime| mime.parse())
  304. .unwrap_or(Ok(mime::APPLICATION_OCTET_STREAM))
  305. .map_err(|_| "unknown content type")?;
  306. let body = if content_type == mime::APPLICATION_OCTET_STREAM {
  307. body.into()
  308. } else if content_type == mime::APPLICATION_JSON {
  309. if cfg!(ipc_custom_protocol) {
  310. serde_json::from_slice::<serde_json::Value>(&body)
  311. .map_err(|e| e.to_string())?
  312. .into()
  313. } else {
  314. // the body is not set if ipc_custom_protocol is not enabled so we'll just ignore it
  315. serde_json::Value::Object(Default::default()).into()
  316. }
  317. } else {
  318. return Err(format!("content type {content_type} is not implemented"));
  319. };
  320. let payload = InvokeRequest {
  321. cmd,
  322. callback,
  323. error,
  324. body,
  325. headers: parts.headers,
  326. };
  327. Ok(payload)
  328. }