Improve extension installation (#48518)

Mikayla Maki , Max , and Piotr created

Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>
Co-authored-by: Piotr <piotr@zed.dev>

Change summary

crates/util/src/archive.rs | 42 ++++++++++++++++++++++++++++-----------
1 file changed, 30 insertions(+), 12 deletions(-)

Detailed changes

crates/util/src/archive.rs 🔗

@@ -6,6 +6,15 @@ use async_zip::base::read;
 use futures::AsyncSeek;
 use futures::{AsyncRead, io::BufReader};
 
+fn archive_path_is_normal(filename: &str) -> bool {
+    Path::new(filename).components().all(|c| {
+        matches!(
+            c,
+            std::path::Component::Normal(_) | std::path::Component::CurDir
+        )
+    })
+}
+
 #[cfg(windows)]
 pub async fn extract_zip<R: AsyncRead + Unpin>(destination: &Path, reader: R) -> Result<()> {
     let mut reader = read::stream::ZipFileReader::new(BufReader::new(reader));
@@ -17,12 +26,17 @@ pub async fn extract_zip<R: AsyncRead + Unpin>(destination: &Path, reader: R) ->
     while let Some(mut item) = reader.next_with_entry().await? {
         let entry_reader = item.reader_mut();
         let entry = entry_reader.entry();
-        let path = destination.join(
-            entry
-                .filename()
-                .as_str()
-                .context("reading zip entry file name")?,
-        );
+        let filename = entry
+            .filename()
+            .as_str()
+            .context("reading zip entry file name")?;
+
+        if !archive_path_is_normal(filename) {
+            reader = item.skip().await.context("reading next zip entry")?;
+            continue;
+        }
+
+        let path = destination.join(filename);
 
         if entry
             .dir()
@@ -79,12 +93,16 @@ pub async fn extract_seekable_zip<R: AsyncRead + AsyncSeek + Unpin>(
         .canonicalize()
         .unwrap_or_else(|_| destination.to_path_buf());
     for (i, entry) in reader.file().entries().to_vec().into_iter().enumerate() {
-        let path = destination.join(
-            entry
-                .filename()
-                .as_str()
-                .context("reading zip entry file name")?,
-        );
+        let filename = entry
+            .filename()
+            .as_str()
+            .context("reading zip entry file name")?;
+
+        if !archive_path_is_normal(filename) {
+            continue;
+        }
+
+        let path = destination.join(filename);
 
         if entry
             .dir()