From 8f715532cbdfe77bdc9e636798e7c5c36aa27f35 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 3 Jan 2022 22:24:35 +0100 Subject: [PATCH] read manifest.toml and add get_webxdc_info() --- assets/icon-webxdc.png | Bin 0 -> 2957 bytes assets/icon-webxdc.svg | 96 +++++++ deltachat-ffi/deltachat.h | 21 ++ deltachat-ffi/src/lib.rs | 24 +- src/webxdc.rs | 254 ++++++++++++++++-- test-data/webxdc/with-manifest-and-icon.xdc | Bin 0 -> 623 bytes ...h-manifest-and-unsupported-icon-format.xdc | Bin 0 -> 1239 bytes test-data/webxdc/with-manifest-empty-name.xdc | Bin 0 -> 354 bytes .../with-manifest-icon-not-existent.xdc | Bin 0 -> 390 bytes test-data/webxdc/with-manifest-no-name.xdc | Bin 0 -> 605 bytes test-data/webxdc/with-minimal-manifest.xdc | Bin 0 -> 458 bytes 11 files changed, 378 insertions(+), 17 deletions(-) create mode 100644 assets/icon-webxdc.png create mode 100644 assets/icon-webxdc.svg create mode 100644 test-data/webxdc/with-manifest-and-icon.xdc create mode 100644 test-data/webxdc/with-manifest-and-unsupported-icon-format.xdc create mode 100644 test-data/webxdc/with-manifest-empty-name.xdc create mode 100644 test-data/webxdc/with-manifest-icon-not-existent.xdc create mode 100644 test-data/webxdc/with-manifest-no-name.xdc create mode 100644 test-data/webxdc/with-minimal-manifest.xdc diff --git a/assets/icon-webxdc.png b/assets/icon-webxdc.png new file mode 100644 index 0000000000000000000000000000000000000000..87c5fa583409755e4daf27217a68686148e03819 GIT binary patch literal 2957 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 literal 0 HcmV?d00001 diff --git a/assets/icon-webxdc.svg b/assets/icon-webxdc.svg new file mode 100644 index 000000000..1164bc39d --- /dev/null +++ b/assets/icon-webxdc.svg @@ -0,0 +1,96 @@ + + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + image/svg+xml + + + + + + + + + + .xdc + diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 3fc5c130c..0736b2415 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -3689,6 +3689,27 @@ char* dc_msg_get_filemime (const dc_msg_t* msg); char* dc_msg_get_webxdc_blob (const dc_msg_t* msg, const char* filename, size_t* ret_bytes); +/** + * Get info a webxdc message, in JSON format. + * The returned JSON string has the following key/values: + * + * - name: The name of the app. + * Defaults to the filename if not set in the manifest. + * - icon: App icon file name. + * Defaults to an standard icon if nothing is set in the manifest. + * To get the file, use dc_msg_get_webxdc_blob(). + * App icons should should be square, + * the implementations will add round corners etc. as needed. + * + * @memberof dc_msg_t + * @param msg The webxdc instance. + * @return a UTF8-encoded JSON string containing all requested info. + * Must be freed using dc_str_unref(). + * NULL is never returned. + */ +char* dc_msg_get_webxdc_info (const dc_msg_t* msg); + + /** * Get the size of the file. Returns the size of the file associated with a * message, if applicable. diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index dd850f2b7..1585432b4 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -3090,7 +3090,6 @@ pub unsafe extern "C" fn dc_msg_get_webxdc_blob( }); match blob { Ok(blob) => { - // TODO: introduce dc_blob_t to avoid malloc and returning size by pointer and to save copying data *ret_bytes = blob.len(); let ptr = libc::malloc(*ret_bytes); libc::memcpy(ptr, blob.as_ptr() as *mut libc::c_void, *ret_bytes); @@ -3103,6 +3102,29 @@ pub unsafe extern "C" fn dc_msg_get_webxdc_blob( } } +#[no_mangle] +pub unsafe extern "C" fn dc_msg_get_webxdc_info(msg: *mut dc_msg_t) -> *mut libc::c_char { + if msg.is_null() { + eprintln!("ignoring careless call to dc_msg_get_webxdc_info()"); + return "".strdup(); + } + let ffi_msg = &*msg; + let ctx = &*ffi_msg.context; + + block_on(async move { + let info = match ffi_msg.message.get_webxdc_info(ctx).await { + Ok(info) => info, + Err(err) => { + error!(ctx, "dc_msg_get_webxdc_info() failed to get info: {}", err); + return "".strdup(); + } + }; + serde_json::to_string(&info) + .unwrap_or_log_default(ctx, "dc_msg_get_webxdc_info() failed to serialise to json") + .strdup() + }) +} + #[no_mangle] pub unsafe extern "C" fn dc_msg_get_filemime(msg: *mut dc_msg_t) -> *mut libc::c_char { if msg.is_null() { diff --git a/src/webxdc.rs b/src/webxdc.rs index 14142469c..f0e451f7a 100644 --- a/src/webxdc.rs +++ b/src/webxdc.rs @@ -16,6 +16,22 @@ use std::convert::TryFrom; use std::io::Read; pub const WEBXDC_SUFFIX: &str = "xdc"; +const WEBXDC_DEFAULT_ICON: &str = "__webxdc__/default-icon.png"; + +/// Raw information read from manifest.toml +#[derive(Debug, Deserialize)] +#[non_exhaustive] +struct WebxdcManifest { + name: Option, + icon: Option, +} + +/// Parsed information from WebxdcManifest and fallbacks. +#[derive(Debug, Serialize)] +pub struct WebxdcInfo { + pub name: String, + pub icon: String, +} /// Status Update ID. #[derive( @@ -227,12 +243,21 @@ impl Context { } } +async fn parse_webxdc_manifest(bytes: &[u8]) -> Result { + let manifest: WebxdcManifest = toml::from_slice(bytes)?; + Ok(manifest) +} + impl Message { /// Return file form inside an archive. /// Currently, this works only if the message is an webxdc instance. pub async fn get_webxdc_blob(&self, context: &Context, name: &str) -> Result> { ensure!(self.viewtype == Viewtype::Webxdc, "No webxdc instance."); + if name == WEBXDC_DEFAULT_ICON { + return Ok(include_bytes!("../assets/icon-webxdc.png").to_vec()); + } + let archive = self .get_file(context) .ok_or_else(|| format_err!("No webxdc instance file."))?; @@ -253,6 +278,58 @@ impl Message { file.read_to_end(&mut buf)?; Ok(buf) } + + /// Return info from manifest.toml or from fallbacks. + pub async fn get_webxdc_info(&self, context: &Context) -> Result { + ensure!(self.viewtype == Viewtype::Webxdc, "No webxdc instance."); + + let mut manifest = if let Ok(bytes) = self.get_webxdc_blob(context, "manifest.toml").await { + if let Ok(manifest) = parse_webxdc_manifest(&bytes).await { + manifest + } else { + WebxdcManifest { + name: None, + icon: None, + } + } + } else { + WebxdcManifest { + name: None, + icon: None, + } + }; + + if let Some(ref name) = manifest.name { + let name = name.trim(); + if name.is_empty() { + warn!(context, "empty name given in manifest"); + manifest.name = None; + } + } + + if let Some(ref icon) = manifest.icon { + if !icon.ends_with(".png") && !icon.ends_with(".jpg") { + warn!(context, "bad icon format \"{}\"; use .png or .jpg", icon); + manifest.icon = None; + } else if self.get_webxdc_blob(context, icon).await.is_err() { + warn!(context, "cannot find icon \"{}\"", icon); + manifest.icon = None; + } + } + + Ok(WebxdcInfo { + name: if let Some(name) = manifest.name { + name + } else { + self.get_filename().unwrap_or_default() + }, + icon: if let Some(icon) = manifest.icon { + icon + } else { + WEBXDC_DEFAULT_ICON.to_string() + }, + }) + } } #[cfg(test)] @@ -305,19 +382,21 @@ mod tests { Ok(()) } - async fn create_webxdc_instance(t: &TestContext) -> Result { - let file = t.get_blobdir().join("minimal.xdc"); - File::create(&file) - .await? - .write_all(include_bytes!("../test-data/webxdc/minimal.xdc")) - .await?; + async fn create_webxdc_instance(t: &TestContext, name: &str, bytes: &[u8]) -> Result { + let file = t.get_blobdir().join(name); + File::create(&file).await?.write_all(bytes).await?; let mut instance = Message::new(Viewtype::File); instance.set_file(file.to_str().unwrap(), None); Ok(instance) } async fn send_webxdc_instance(t: &TestContext, chat_id: ChatId) -> Result { - let mut instance = create_webxdc_instance(t).await?; + let mut instance = create_webxdc_instance( + t, + "minimal.xdc", + include_bytes!("../test-data/webxdc/minimal.xdc"), + ) + .await?; let instance_msg_id = send_msg(t, chat_id, &mut instance).await?; Message::load_from_db(t, instance_msg_id).await } @@ -379,7 +458,12 @@ mod tests { let t = TestContext::new_alice().await; let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?; - let mut instance = create_webxdc_instance(&t).await?; + let mut instance = create_webxdc_instance( + &t, + "minimal.xdc", + include_bytes!("../test-data/webxdc/minimal.xdc"), + ) + .await?; chat_id.set_draft(&t, Some(&mut instance)).await?; let instance = chat_id.get_draft(&t).await?.unwrap(); t.send_webxdc_status_update(instance.id, "descr", "42") @@ -603,7 +687,12 @@ mod tests { // prepare webxdc instance, // status updates are not sent for drafts, therefore send_webxdc_status_update() returns Ok(None) - let mut alice_instance = create_webxdc_instance(&alice).await?; + let mut alice_instance = create_webxdc_instance( + &alice, + "minimal.xdc", + include_bytes!("../test-data/webxdc/minimal.xdc"), + ) + .await?; alice_chat_id .set_draft(&alice, Some(&mut alice_instance)) .await?; @@ -677,6 +766,18 @@ mod tests { Ok(()) } + #[async_std::test] + async fn test_get_webxdc_blob_default_icon() -> Result<()> { + let t = TestContext::new_alice().await; + let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?; + let instance = send_webxdc_instance(&t, chat_id).await?; + + let buf = instance.get_webxdc_blob(&t, WEBXDC_DEFAULT_ICON).await?; + assert!(buf.len() > 100); + assert!(String::from_utf8_lossy(&buf).contains("PNG\r\n")); + Ok(()) + } + #[async_std::test] async fn test_get_webxdc_blob_with_absolute_paths() -> Result<()> { let t = TestContext::new_alice().await; @@ -694,13 +795,12 @@ mod tests { async fn test_get_webxdc_blob_with_subdirs() -> Result<()> { let t = TestContext::new_alice().await; let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?; - let file = t.get_blobdir().join("some-files.xdc"); - File::create(&file) - .await? - .write_all(include_bytes!("../test-data/webxdc/some-files.xdc")) - .await?; - let mut instance = Message::new(Viewtype::Webxdc); - instance.set_file(file.to_str().unwrap(), None); + let mut instance = create_webxdc_instance( + &t, + "some-files.xdc", + include_bytes!("../test-data/webxdc/some-files.xdc"), + ) + .await?; chat_id.set_draft(&t, Some(&mut instance)).await?; let buf = instance.get_webxdc_blob(&t, "index.html").await?; @@ -731,4 +831,126 @@ mod tests { Ok(()) } + + #[async_std::test] + async fn test_parse_webxdc_manifest() -> Result<()> { + let result = parse_webxdc_manifest(r#"key = syntax error"#.as_bytes()).await; + assert!(result.is_err()); + + let manifest = parse_webxdc_manifest(r#"no_name = "no name, no icon""#.as_bytes()).await?; + assert_eq!(manifest.name, None); + assert_eq!(manifest.icon, None); + + let manifest = parse_webxdc_manifest(r#"name = "name, no icon""#.as_bytes()).await?; + assert_eq!(manifest.name, Some("name, no icon".to_string())); + assert_eq!(manifest.icon, None); + + let manifest = parse_webxdc_manifest( + r#"name = "foo" +icon = "bar""# + .as_bytes(), + ) + .await?; + assert_eq!(manifest.name, Some("foo".to_string())); + assert_eq!(manifest.icon, Some("bar".to_string())); + + let manifest = parse_webxdc_manifest( + r#"name = "foz" +icon = "baz" +add_item = "that should be just ignored" + +[section] +sth_for_the = "future""# + .as_bytes(), + ) + .await?; + assert_eq!(manifest.name, Some("foz".to_string())); + assert_eq!(manifest.icon, Some("baz".to_string())); + + Ok(()) + } + + #[async_std::test] + async fn test_get_webxdc_info() -> Result<()> { + let t = TestContext::new_alice().await; + let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?; + + let instance = send_webxdc_instance(&t, chat_id).await?; + let info = instance.get_webxdc_info(&t).await?; + assert_eq!(info.name, "minimal.xdc"); + assert_eq!(info.icon, WEBXDC_DEFAULT_ICON.to_string()); + + let mut instance = create_webxdc_instance( + &t, + "with-manifest-empty-name.xdc", + include_bytes!("../test-data/webxdc/with-manifest-empty-name.xdc"), + ) + .await?; + chat_id.set_draft(&t, Some(&mut instance)).await?; + let info = instance.get_webxdc_info(&t).await?; + assert_eq!(info.name, "with-manifest-empty-name.xdc"); + assert_eq!(info.icon, WEBXDC_DEFAULT_ICON.to_string()); + + let mut instance = create_webxdc_instance( + &t, + "with-manifest-no-name.xdc", + include_bytes!("../test-data/webxdc/with-manifest-no-name.xdc"), + ) + .await?; + chat_id.set_draft(&t, Some(&mut instance)).await?; + let info = instance.get_webxdc_info(&t).await?; + assert_eq!(info.name, "with-manifest-no-name.xdc"); + assert_eq!(info.icon, "some.png".to_string()); + + let mut instance = create_webxdc_instance( + &t, + "with-minimal-manifest.xdc", + include_bytes!("../test-data/webxdc/with-minimal-manifest.xdc"), + ) + .await?; + chat_id.set_draft(&t, Some(&mut instance)).await?; + let info = instance.get_webxdc_info(&t).await?; + assert_eq!(info.name, "nice app!"); + assert_eq!(info.icon, WEBXDC_DEFAULT_ICON.to_string()); + + let mut instance = create_webxdc_instance( + &t, + "with-manifest-icon-not-existent.xdc", + include_bytes!("../test-data/webxdc/with-manifest-icon-not-existent.xdc"), + ) + .await?; + chat_id.set_draft(&t, Some(&mut instance)).await?; + let info = instance.get_webxdc_info(&t).await?; + assert_eq!(info.name, "with bad icon"); + assert_eq!(info.icon, WEBXDC_DEFAULT_ICON.to_string()); + + let mut instance = create_webxdc_instance( + &t, + "with-manifest-and-icon.xdc", + include_bytes!("../test-data/webxdc/with-manifest-and-icon.xdc"), + ) + .await?; + chat_id.set_draft(&t, Some(&mut instance)).await?; + let info = instance.get_webxdc_info(&t).await?; + assert_eq!(info.name, "with some icon"); + assert_eq!(info.icon, "some.png"); + + let mut instance = create_webxdc_instance( + &t, + "with-manifest-and-unsupported-icon-format.xdc", + include_bytes!("../test-data/webxdc/with-manifest-and-unsupported-icon-format.xdc"), + ) + .await?; + chat_id.set_draft(&t, Some(&mut instance)).await?; + let info = instance.get_webxdc_info(&t).await?; + assert_eq!(info.name, "with tiff icon"); + assert_eq!(info.icon, WEBXDC_DEFAULT_ICON); + + let msg_id = send_text_msg(&t, chat_id, "foo".to_string()).await?; + let msg = Message::load_from_db(&t, msg_id).await?; + let result = msg.get_webxdc_info(&t).await; + assert!(result.is_err()); + + Ok(()) + } } diff --git a/test-data/webxdc/with-manifest-and-icon.xdc b/test-data/webxdc/with-manifest-and-icon.xdc new file mode 100644 index 0000000000000000000000000000000000000000..96291b4702aca4f899400f9488204688e1b5b78c GIT binary patch literal 623 zcmWIWW@h1HU}9ik;IB~$QEB15E&^nOFc*UiLuOt|YK2}#Np4PP2qyz`ZPb;-qd;6* z!Og(P@|BT+fd!<}M$yIJIV3W`RRN^Zjw>&-f-3-Mt_aXv4u+4LRYF)Y=ABdls?-8v zUZAL0&r812Ma&-f-3-Mt_aXv4hG+yDj^)b6RxNMRcZk- zFVNiF#JtS3)Z!Aol6^yadE(MxXXR;~TmmG(hXT&dC6clRnLDYfId%lRzJdvNHg!mtiQ*&rQ`U$xH)#A?3)G#55pA zvuA0DZ-%g=NZoGFY!B|dx}K$-5&_u)7ac^R_pFg(TG$>I9k?RF=~wdFTNi#zZZ_ra zGSB7LlCzm|E!a^&M1)sWJ<#!mqrVep;6je14+<-emZsgCT~hw>`GW&*s_*alzVG|9 zvuBoFzOBH{@~}E(>wcDKD#ci6!3%beGNkwx5%@V8&^h@ zOfBJ`t=E0eXIHoH{6~3ferL`-Un*wnvg_cps!#o!Zmu)o{A)cs@lxow&WI#)4XrH( zFJqYn7MD)-o%?bRgMRdj%|AYGjCG7XwNmr)o1512?|79xK7IDEwrYb}=U-p>I-@^t zYu2{@4(C*t)91WXZF~9n-{s6Z+GLOX+vRobPvIj@ z9fkdCqCe^LEpe@}?6CO%VvX*6xq@e>Z-0_ZVRzX5WU|eihJ%$-3{mWG;hu3(|KPz_s604_q!@?)^GoO=Y70s#XhBj zHw^cb8=as3qcp1DCA(rB<8FD2A5!lp-rKn6e9*H_{oR~DfBY=2QIh1*6x+8r_eHp+ zsNie~G1+zC(w0+s|CaG}G|O%pKukm8*?4^6gz`&ahE>^gR0G z{mAmfAEKKNyi@;uG4)j3Q*-n8QLANNXK#G>PPcB&^hZnL=e;>CZg}5~J$|qBZw>d> zd;aV{r>|HS+inz?eMd(5k$&cR?VN_1(D|AFg^KTH{l9+T@A_Z*SNXE)yUhzdSPmb) zFyC)!!k3b=lovlT4!+!PKU-RQri6WYnm`?6fHxzP95b#QEdk7b3=F`0%dn&o#6r&B ztdRVTmN6ja;mXO#=0yO_L(A7dBSHBZkCB-96=EdFaZ4JH0!c*f1)2uRy%?skvVl}H N1K~#?eSsOo0|0hP?P~x4 literal 0 HcmV?d00001 diff --git a/test-data/webxdc/with-manifest-empty-name.xdc b/test-data/webxdc/with-manifest-empty-name.xdc new file mode 100644 index 0000000000000000000000000000000000000000..f720a1269048ba7cefd928b6791f6714449e28a3 GIT binary patch literal 354 zcmWIWW@h1HU}9ik;IB~$QEB15E&^nOFc*UiLuOt|YK2}#Np4PP2qyz`ZPb;-qd;6* z!Og(P@|BT+fd!<}M$yIJIV3W`RRN^Zjw>&-f-3-St~rBh$k#8iGh85nqh=H@2m zWu~PTm*|z`Lyf+9@k-(?AVxDfFEKY&!B#;@31}cAlN>WHr%C_~XJ7z2h+#=1h=uHM qR*1vVOhz^j(}~FDMF7o1b1=|Ikb^OdWMu1ZXY?gBsA>0`{v>dO($iK+Fp? zH#adaGcC2aM6VNLDtG&-f-3-SZVQ8I$fX<}At9hj5atD%o12)I znU-2yqF0g+H9Gy$mBdUSMl(7yIX_RqRzayaKQ~pcATM1BY_JH>!5j=5HmQUx5kBsb z3^X(yh&g};!_G8`)5{C+He;)t;vu98B)=PCcv!_dB-6;I-&@W|vxXf?n zyXBg*(i>h$*u?&gxn`+q!pIQd&B!Fjj4NCvfR1Hg0EQmJl12~$VDxg#%?yMefpi$icMJgc CK%DRZ literal 0 HcmV?d00001 diff --git a/test-data/webxdc/with-minimal-manifest.xdc b/test-data/webxdc/with-minimal-manifest.xdc new file mode 100644 index 0000000000000000000000000000000000000000..76925ffa86e23890cf37bacab9ba904289fdbddf GIT binary patch literal 458 zcmWIWW@Zs#U}E54c)CM5B*~>Bs+fU+VImN7F~~4v=B1=o=w+1T=7ffDGBEcRUryWw z#HAJ742&#a85tOWdKnlt2bO#+=j-Gt2tA>3^6bXPn;HXGc50Tza5*GQo*gx5#e_K% zBSPjxC9MvLnzSr?;S&C-+J1VcbTk4r^t`r(YWe%F^61~m+{C=hwAA7fy^?&W%U1$jy$aLid5O8H3bqPLd6~(n3W)^;ib`Ao-i%Cg%(#L? z0%!vR1JL6POBz8e?bm F3;+;0eOLeh literal 0 HcmV?d00001