mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
read manifest.toml and add get_webxdc_info()
This commit is contained in:
BIN
assets/icon-webxdc.png
Normal file
BIN
assets/icon-webxdc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
96
assets/icon-webxdc.svg
Normal file
96
assets/icon-webxdc.svg
Normal file
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:export-ydpi="409.60001"
|
||||
inkscape:export-xdpi="409.60001"
|
||||
inkscape:export-filename="/Users/bpetersen/projects/deltachat-core-rust/assets/icon-webxdc.png"
|
||||
version="1.0"
|
||||
width="60"
|
||||
height="60"
|
||||
viewBox="0 0 45 45"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
id="svg4344"
|
||||
sodipodi:docname="icon-webxdc.svg"
|
||||
inkscape:version="1.0.2 (e86c8708, 2021-01-15)">
|
||||
<defs
|
||||
id="defs4348" />
|
||||
<sodipodi:namedview
|
||||
inkscape:snap-global="false"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
inkscape:document-rotation="0"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1864"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview4346"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:zoom="7.919596"
|
||||
inkscape:cx="37.391123"
|
||||
inkscape:cy="24.898474"
|
||||
inkscape:window-x="45"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4344" />
|
||||
<metadata
|
||||
id="metadata4336">
|
||||
Created by potrace 1.15, written by Peter Selinger 2001-2017
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<rect
|
||||
y="-4.4408921e-16"
|
||||
x="0"
|
||||
height="45"
|
||||
width="45"
|
||||
id="rect860"
|
||||
style="opacity:1;fill:#b2bbbe;fill-opacity:1;stroke-width:0.819271" />
|
||||
<g
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
transform="matrix(0.00255113,0,0,-0.00255113,5.586152,38.200477)"
|
||||
id="g4342">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:6271.73px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:293.987"
|
||||
x="-254.26019"
|
||||
y="-3819.2668"
|
||||
id="text865"
|
||||
transform="scale(1,-1)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan863"
|
||||
x="-254.26019"
|
||||
y="-3819.2668"
|
||||
style="font-size:6271.73px;stroke-width:293.987" /></text>
|
||||
</g>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:18px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
|
||||
x="1.125"
|
||||
y="29.20166"
|
||||
id="text869"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan867"
|
||||
x="1.125"
|
||||
y="29.20166"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:18px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';stroke-width:0.75">.xdc</tspan></text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -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.
|
||||
|
||||
@@ -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() {
|
||||
|
||||
254
src/webxdc.rs
254
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<String>,
|
||||
icon: Option<String>,
|
||||
}
|
||||
|
||||
/// 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<WebxdcManifest> {
|
||||
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<Vec<u8>> {
|
||||
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<WebxdcInfo> {
|
||||
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<Message> {
|
||||
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<Message> {
|
||||
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<Message> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
BIN
test-data/webxdc/with-manifest-and-icon.xdc
Normal file
BIN
test-data/webxdc/with-manifest-and-icon.xdc
Normal file
Binary file not shown.
BIN
test-data/webxdc/with-manifest-and-unsupported-icon-format.xdc
Normal file
BIN
test-data/webxdc/with-manifest-and-unsupported-icon-format.xdc
Normal file
Binary file not shown.
BIN
test-data/webxdc/with-manifest-empty-name.xdc
Normal file
BIN
test-data/webxdc/with-manifest-empty-name.xdc
Normal file
Binary file not shown.
BIN
test-data/webxdc/with-manifest-icon-not-existent.xdc
Normal file
BIN
test-data/webxdc/with-manifest-icon-not-existent.xdc
Normal file
Binary file not shown.
BIN
test-data/webxdc/with-manifest-no-name.xdc
Normal file
BIN
test-data/webxdc/with-manifest-no-name.xdc
Normal file
Binary file not shown.
BIN
test-data/webxdc/with-minimal-manifest.xdc
Normal file
BIN
test-data/webxdc/with-minimal-manifest.xdc
Normal file
Binary file not shown.
Reference in New Issue
Block a user