Skip to content

Commit 58cf6ea

Browse files
authored
Merge pull request #197 from Open-VCS/Fix-plugin-issues
Fix plugin issues
2 parents 9e12fb9 + 7b28d20 commit 58cf6ea

10 files changed

Lines changed: 374 additions & 368 deletions

File tree

Backend/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ serde_json = "1.0"
4343
time = { version = "0.3", features = ["local-offset"] }
4444
zip = "8.3"
4545
tar = "0.4"
46-
xz2 = "0.1"
46+
flate2 = "1"
4747
shlex = "1.2"
4848
sha2 = "0.10"
4949
hex = "0.4"

Backend/built-in-plugins/Git

Backend/src/lib.rs

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
//! plugin discovery, and startup behavior.
77
88
use log::warn;
9-
use std::path::PathBuf;
109
use std::sync::Arc;
1110
use tauri::path::BaseDirectory;
1211
use tauri::WindowEvent;
@@ -107,36 +106,6 @@ fn try_reopen_last_repo<R: tauri::Runtime>(app_handle: &tauri::AppHandle<R>) {
107106
}
108107
}
109108

110-
/// Resolves a development fallback path for the bundled Node runtime.
111-
///
112-
/// In `cargo tauri dev`, the generated runtime is placed under
113-
/// `target/openvcs/node-runtime`, while Tauri resource resolution can point at
114-
/// `target/debug/node-runtime`. This helper probes the generated location.
115-
///
116-
/// # Returns
117-
/// - `Some(PathBuf)` when the dev bundled node binary exists.
118-
/// - `None` when the path cannot be derived or does not exist.
119-
fn resolve_dev_bundled_node_fallback() -> Option<PathBuf> {
120-
let exe = std::env::current_exe().ok()?;
121-
let exe_dir = exe.parent()?;
122-
let target_dir = exe_dir.parent()?;
123-
let node_name = if cfg!(windows) { "node.exe" } else { "node" };
124-
let candidate = target_dir
125-
.join("openvcs")
126-
.join("node-runtime")
127-
.join(node_name);
128-
if candidate.is_file() {
129-
return Some(candidate);
130-
}
131-
let nested = exe_dir
132-
.join("_up_")
133-
.join("target")
134-
.join("openvcs")
135-
.join("node-runtime")
136-
.join(node_name);
137-
nested.is_file().then_some(nested)
138-
}
139-
140109
/// Starts the OpenVCS backend runtime and Tauri application.
141110
///
142111
/// This configures logging, plugin bundle synchronization, startup restore
@@ -187,17 +156,18 @@ pub fn run() {
187156
);
188157
}
189158
}
190-
let node_name = if cfg!(windows) { "node.exe" } else { "node" };
191-
let mut node_candidates: Vec<PathBuf> = Vec::new();
192159
if let Ok(node_runtime_dir) = app.path().resolve("node-runtime", BaseDirectory::Resource)
193160
{
194-
node_candidates.push(node_runtime_dir.join(node_name));
195-
}
196-
if let Some(dev_fallback) = resolve_dev_bundled_node_fallback() {
197-
if !node_candidates.iter().any(|p| p == &dev_fallback) {
198-
node_candidates.push(dev_fallback);
161+
crate::plugin_paths::set_node_runtime_resource_dir(node_runtime_dir.clone());
162+
if let Some(parent) = node_runtime_dir.parent() {
163+
crate::plugin_paths::set_resource_dir(parent.to_path_buf());
199164
}
200165
}
166+
// Keep resource lookup state populated before resolving bundled Node
167+
// candidates. `bundled_node_candidate_paths()` uses both the generic
168+
// RESOURCE_DIR base and the exact Tauri-resolved `node-runtime`
169+
// directory captured above, so future refactors must preserve this order.
170+
let node_candidates = crate::plugin_paths::bundled_node_candidate_paths();
201171

202172
if let Some(bundled_node) = node_candidates.iter().find(|path| path.is_file()) {
203173
crate::plugin_paths::set_node_executable_path(bundled_node.to_path_buf());

Backend/src/plugin_bundles.rs

Lines changed: 75 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,60 @@
44
55
use crate::logging::LogTimer;
66
use crate::plugin_paths::{built_in_plugin_dirs, ensure_dir, plugins_dir, PLUGIN_MANIFEST_NAME};
7+
use flate2::read::GzDecoder;
78
use log::{debug, error, info, trace, warn};
89
use serde::{Deserialize, Serialize};
910
use sha2::{Digest, Sha256};
1011
use std::collections::{BTreeMap, HashSet};
1112
use std::fs;
12-
use std::io::{Read, Write};
13+
use std::io::{Read, Seek, SeekFrom, Write};
1314
use std::path::{Component, Path, PathBuf};
1415
use std::sync::OnceLock;
15-
use xz2::read::XzDecoder;
1616

1717
const MODULE: &str = "plugin_bundles";
18+
const GZIP_MAGIC: [u8; 2] = [0x1F, 0x8B];
19+
20+
/// Opens a plugin bundle as a decompressed tar reader.
21+
///
22+
/// `.ovcsp` bundles are gzip-compressed tar archives.
23+
///
24+
/// # Parameters
25+
/// - `bundle_path`: Bundle file path.
26+
///
27+
/// # Returns
28+
/// - `Ok(Box<dyn Read>)` with a decompressed tar stream.
29+
/// - `Err(String)` when the bundle cannot be opened or has an unknown format.
30+
fn open_bundle_reader(bundle_path: &Path) -> Result<Box<dyn Read>, String> {
31+
let mut file =
32+
fs::File::open(bundle_path).map_err(|e| format!("open {}: {e}", bundle_path.display()))?;
33+
let mut magic = [0u8; 6];
34+
let read = file
35+
.read(&mut magic)
36+
.map_err(|e| format!("read {}: {e}", bundle_path.display()))?;
37+
file.seek(SeekFrom::Start(0))
38+
.map_err(|e| format!("seek {}: {e}", bundle_path.display()))?;
39+
40+
if read >= GZIP_MAGIC.len() && magic[..GZIP_MAGIC.len()] == GZIP_MAGIC {
41+
return Ok(Box::new(GzDecoder::new(file)));
42+
}
43+
Err(format!(
44+
"unsupported bundle compression in {}",
45+
bundle_path.display()
46+
))
47+
}
48+
49+
/// Opens a plugin bundle as a tar archive.
50+
///
51+
/// # Parameters
52+
/// - `bundle_path`: Bundle file path.
53+
///
54+
/// # Returns
55+
/// - `Ok(tar::Archive<...>)` when the bundle format is supported.
56+
/// - `Err(String)` when the bundle cannot be decoded.
57+
fn open_bundle_archive(bundle_path: &Path) -> Result<tar::Archive<Box<dyn Read>>, String> {
58+
let reader = open_bundle_reader(bundle_path)?;
59+
Ok(tar::Archive::new(reader))
60+
}
1861

1962
/// Safety and resource limits enforced during bundle extraction.
2063
#[derive(Debug, Clone, Copy)]
@@ -326,7 +369,7 @@ impl PluginBundleStore {
326369
&bundle_sha256[..12]
327370
);
328371

329-
let (manifest_bundle_path, manifest) = locate_manifest_tar_xz(bundle_path)?;
372+
let (manifest_bundle_path, manifest) = locate_manifest_in_bundle(bundle_path)?;
330373
let plugin_id = manifest.id.trim().to_string();
331374
if plugin_id.is_empty() {
332375
error!("install_ovcsp_with_limits: manifest id is empty",);
@@ -385,10 +428,8 @@ impl PluginBundleStore {
385428
.map_err(|e| format!("canonicalize {}: {e}", staging_version_dir.display()))?;
386429

387430
// Extract all entries under `<pluginId>/...` into the staging version directory.
388-
let f = fs::File::open(bundle_path)
389-
.map_err(|e| format!("open {}: {e}", bundle_path.display()))?;
390-
let decoder = XzDecoder::new(f);
391-
let mut tar = tar::Archive::new(decoder);
431+
let mut tar = open_bundle_archive(bundle_path)
432+
.map_err(|e| format!("open bundle archive {}: {e}", bundle_path.display()))?;
392433

393434
for entry in tar.entries().map_err(|e| format!("read tar: {e}"))? {
394435
let mut entry = entry.map_err(|e| format!("tar entry: {e}"))?;
@@ -696,7 +737,7 @@ impl PluginBundleStore {
696737
trace!("ensure_built_in_bundle: {}", bundle_path.display());
697738

698739
let bundle_sha256 = sha256_hex_file(bundle_path)?;
699-
let (_manifest_path, manifest) = locate_manifest_tar_xz(bundle_path)?;
740+
let (_manifest_path, manifest) = locate_manifest_in_bundle(bundle_path)?;
700741
let plugin_id = manifest.id.trim();
701742
if plugin_id.is_empty() {
702743
error!("ensure_built_in_bundle: bundle manifest id is empty",);
@@ -1171,7 +1212,7 @@ fn read_built_in_plugin_ids() -> HashSet<String> {
11711212
let mut out: HashSet<String> = HashSet::new();
11721213

11731214
for bundle_path in builtin_bundle_paths() {
1174-
let (_manifest_path, manifest) = match locate_manifest_tar_xz(&bundle_path) {
1215+
let (_manifest_path, manifest) = match locate_manifest_in_bundle(&bundle_path) {
11751216
Ok(v) => v,
11761217
Err(err) => {
11771218
warn!(
@@ -1240,19 +1281,17 @@ fn derive_install_version(manifest: &PluginManifest, bundle_sha256: &str) -> Str
12401281
.unwrap_or_else(|| format!("sha256-{}", &bundle_sha256[..12]))
12411282
}
12421283

1243-
/// Finds and parses `openvcs.plugin.json` from a tar.xz bundle.
1284+
/// Finds and parses `openvcs.plugin.json` from a plugin bundle archive.
12441285
///
12451286
/// # Parameters
12461287
/// - `bundle_path`: Bundle file path.
12471288
///
12481289
/// # Returns
12491290
/// - `Ok((PathBuf, PluginManifest))` manifest path inside archive and manifest payload.
12501291
/// - `Err(String)` on read/parse/validation failure.
1251-
fn locate_manifest_tar_xz(bundle_path: &Path) -> Result<(PathBuf, PluginManifest), String> {
1252-
let file =
1253-
fs::File::open(bundle_path).map_err(|e| format!("open {}: {e}", bundle_path.display()))?;
1254-
let decoder = XzDecoder::new(file);
1255-
let mut tar = tar::Archive::new(decoder);
1292+
fn locate_manifest_in_bundle(bundle_path: &Path) -> Result<(PathBuf, PluginManifest), String> {
1293+
let mut tar = open_bundle_archive(bundle_path)
1294+
.map_err(|e| format!("open bundle archive {}: {e}", bundle_path.display()))?;
12561295

12571296
let mut manifest_path: Option<PathBuf> = None;
12581297
let mut manifest_json: Option<Vec<u8>> = None;
@@ -1382,9 +1421,9 @@ fn validate_entrypoint(version_dir: &Path, exec: Option<&str>, label: &str) -> R
13821421
#[cfg(test)]
13831422
mod tests {
13841423
use super::*;
1385-
use std::io::Cursor;
1424+
use flate2::write::GzEncoder;
1425+
use flate2::Compression;
13861426
use tempfile::tempdir;
1387-
use xz2::write::XzEncoder;
13881427

13891428
/// Synthetic tar entry kind used by bundle-construction helpers.
13901429
enum TarEntryKind {
@@ -1406,16 +1445,15 @@ mod tests {
14061445
kind: TarEntryKind,
14071446
}
14081447

1409-
/// Builds a tar.xz bundle from synthetic test entries.
1448+
/// Builds a tar.gz bundle from synthetic test entries.
14101449
///
14111450
/// # Parameters
14121451
/// - `entries`: Tar entries to include.
14131452
///
14141453
/// # Returns
1415-
/// - Encoded tar.xz bytes.
1416-
fn make_tar_xz_bundle(entries: Vec<TarEntry>) -> Vec<u8> {
1417-
let cursor = Cursor::new(Vec::<u8>::new());
1418-
let encoder = XzEncoder::new(cursor, 6);
1454+
/// - Encoded tar.gz bytes.
1455+
fn make_tar_gz_bundle(entries: Vec<TarEntry>) -> Vec<u8> {
1456+
let encoder = GzEncoder::new(Vec::<u8>::new(), Compression::default());
14191457
let mut tar = tar::Builder::new(encoder);
14201458

14211459
for e in entries {
@@ -1446,17 +1484,17 @@ mod tests {
14461484
}
14471485

14481486
let encoder = tar.into_inner().unwrap();
1449-
encoder.finish().unwrap().into_inner()
1487+
encoder.finish().unwrap()
14501488
}
14511489

1452-
/// Builds a minimal raw tar.xz bundle from `(path, bytes)` tuples.
1490+
/// Builds a minimal raw tar.gz bundle from `(path, bytes)` tuples.
14531491
///
14541492
/// # Parameters
14551493
/// - `entries`: Raw path/data entries.
14561494
///
14571495
/// # Returns
1458-
/// - Encoded tar.xz bytes.
1459-
fn make_raw_tar_xz_bundle(entries: Vec<(String, Vec<u8>)>) -> Vec<u8> {
1496+
/// - Encoded tar.gz bytes.
1497+
fn make_raw_tar_gz_bundle(entries: Vec<(String, Vec<u8>)>) -> Vec<u8> {
14601498
/// Writes an octal tar header field.
14611499
///
14621500
/// # Parameters
@@ -1524,7 +1562,7 @@ mod tests {
15241562
tar_bytes.resize(tar_bytes.len() + 1024, 0u8);
15251563

15261564
let mut out = Vec::<u8>::new();
1527-
let mut enc = XzEncoder::new(&mut out, 6);
1565+
let mut enc = GzEncoder::new(&mut out, Compression::default());
15281566
enc.write_all(&tar_bytes).unwrap();
15291567
enc.finish().unwrap();
15301568
out
@@ -1576,7 +1614,7 @@ mod tests {
15761614
kind: TarEntryKind::File,
15771615
});
15781616
}
1579-
let bundle = make_tar_xz_bundle(entries);
1617+
let bundle = make_tar_gz_bundle(entries);
15801618

15811619
let (_tmp, bundle_path) = write_bundle_to_temp(&bundle);
15821620
let store_root = tempdir().unwrap();
@@ -1598,7 +1636,7 @@ mod tests {
15981636
/// # Returns
15991637
/// - `()`.
16001638
fn install_requires_manifest_at_expected_location() {
1601-
let bundle = make_tar_xz_bundle(vec![TarEntry {
1639+
let bundle = make_tar_gz_bundle(vec![TarEntry {
16021640
name: "test.plugin/other.json".into(),
16031641
data: b"{}".to_vec(),
16041642
unix_mode: None,
@@ -1619,7 +1657,7 @@ mod tests {
16191657
/// # Returns
16201658
/// - `()`.
16211659
fn install_validates_declared_entrypoints_exist() {
1622-
let bundle = make_tar_xz_bundle(vec![TarEntry {
1660+
let bundle = make_tar_gz_bundle(vec![TarEntry {
16231661
name: "test.plugin/openvcs.plugin.json".into(),
16241662
data: basic_manifest(
16251663
"test.plugin",
@@ -1643,7 +1681,7 @@ mod tests {
16431681
/// # Returns
16441682
/// - `()`.
16451683
fn install_rejects_functions_component() {
1646-
let bundle = make_tar_xz_bundle(vec![TarEntry {
1684+
let bundle = make_tar_gz_bundle(vec![TarEntry {
16471685
name: "test.plugin/openvcs.plugin.json".into(),
16481686
data: basic_manifest("test.plugin", ",\"functions\":{\"exec\":\"legacy.mjs\"}"),
16491687
unix_mode: None,
@@ -1662,12 +1700,12 @@ mod tests {
16621700
}
16631701

16641702
#[test]
1665-
/// Verifies installer accepts valid tar.xz bundles.
1703+
/// Verifies installer accepts valid tar.gz bundles.
16661704
///
16671705
/// # Returns
16681706
/// - `()`.
1669-
fn install_accepts_tar_xz_bundles() {
1670-
let bundle = make_tar_xz_bundle(vec![
1707+
fn install_accepts_tar_gz_bundles() {
1708+
let bundle = make_tar_gz_bundle(vec![
16711709
TarEntry {
16721710
name: "test.plugin/openvcs.plugin.json".into(),
16731711
data: basic_manifest(
@@ -1700,7 +1738,7 @@ mod tests {
17001738
/// # Returns
17011739
/// - `()`.
17021740
fn install_rejects_tar_zipslip_parent_dir() {
1703-
let bundle = make_raw_tar_xz_bundle(vec![
1741+
let bundle = make_raw_tar_gz_bundle(vec![
17041742
(
17051743
"test.plugin/openvcs.plugin.json".into(),
17061744
basic_manifest("test.plugin", ""),
@@ -1722,7 +1760,7 @@ mod tests {
17221760
/// # Returns
17231761
/// - `()`.
17241762
fn install_rejects_tar_symlink_entries() {
1725-
let bundle = make_tar_xz_bundle(vec![
1763+
let bundle = make_tar_gz_bundle(vec![
17261764
TarEntry {
17271765
name: "test.plugin/openvcs.plugin.json".into(),
17281766
data: basic_manifest("test.plugin", ""),
@@ -1754,7 +1792,7 @@ mod tests {
17541792
/// - `()`.
17551793
fn install_rejects_tar_suspicious_compression_ratio() {
17561794
let big = vec![0u8; 2 * 1024 * 1024];
1757-
let bundle = make_tar_xz_bundle(vec![
1795+
let bundle = make_tar_gz_bundle(vec![
17581796
TarEntry {
17591797
name: "test.plugin/openvcs.plugin.json".into(),
17601798
data: basic_manifest("test.plugin", ""),

0 commit comments

Comments
 (0)