aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/util/zip.rs
blob: 9c9135d66c8480e54a2155d150f1ead952d0e601 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//! ref: composer/src/Composer/Util/Zip.php

use anyhow::Result;
use indexmap::IndexMap;
use shirabe_php_shim::{dirname, extension_loaded, implode, stream_get_contents, RuntimeException, ZipArchive};

pub struct Zip;

impl Zip {
    pub fn get_composer_json(path_to_zip: &str) -> Result<Option<String>> {
        if !extension_loaded("zip") {
            return Err(RuntimeException {
                message: "The Zip Util requires PHP's zip extension".to_string(),
                code: 0,
            }
            .into());
        }

        let mut zip = ZipArchive::new();
        if zip.open(path_to_zip, 0).is_err() {
            return Ok(None);
        }

        if zip.num_files == 0 {
            zip.close();
            return Ok(None);
        }

        let found_file_index = Self::locate_file(&zip, "composer.json")?;

        let mut content: Option<String> = None;
        let configuration_file_name = zip.get_name_index(found_file_index);
        let stream = zip.get_stream(&configuration_file_name);

        if stream.is_some() {
            content = stream_get_contents(stream.unwrap());
        }

        zip.close();

        Ok(content)
    }

    fn locate_file(zip: &ZipArchive, filename: &str) -> Result<i64> {
        // return root composer.json if it is there and is a file
        if let Some(index) = zip.locate_name(filename) {
            if zip.get_from_index(index).is_some() {
                return Ok(index);
            }
        }

        let mut top_level_paths: IndexMap<String, bool> = IndexMap::new();
        for i in 0..zip.num_files {
            let name = zip.get_name_index(i);
            let dir_name = dirname(&name);

            // ignore OSX specific resource fork folder
            if name.contains("__MACOSX") {
                continue;
            }

            // handle archives with proper TOC
            if dir_name == "." {
                top_level_paths.insert(name, true);
                if top_level_paths.len() > 1 {
                    return Err(RuntimeException {
                        message: format!(
                            "Archive has more than one top level directories, and no composer.json was found on the top level, so it's an invalid archive. Top level paths found were: {}",
                            implode(",", &top_level_paths.keys().cloned().collect::<Vec<_>>())
                        ),
                        code: 0,
                    }
                    .into());
                }
                continue;
            }

            // handle archives which do not have a TOC record for the directory itself
            if !dir_name.contains('\\') && !dir_name.contains('/') {
                top_level_paths.insert(format!("{}/", dir_name), true);
                if top_level_paths.len() > 1 {
                    return Err(RuntimeException {
                        message: format!(
                            "Archive has more than one top level directories, and no composer.json was found on the top level, so it's an invalid archive. Top level paths found were: {}",
                            implode(",", &top_level_paths.keys().cloned().collect::<Vec<_>>())
                        ),
                        code: 0,
                    }
                    .into());
                }
            }
        }

        if !top_level_paths.is_empty() {
            let first_key = top_level_paths.keys().next().unwrap().clone();
            if let Some(index) = zip.locate_name(&format!("{}{}", first_key, filename)) {
                if zip.get_from_index(index).is_some() {
                    return Ok(index);
                }
            }
        }

        Err(RuntimeException {
            message: "No composer.json found either at the top level or within the topmost directory"
                .to_string(),
            code: 0,
        }
        .into())
    }
}