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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
//! ref: composer/src/Composer/Package/Archiver/PharArchiver.php
use indexmap::IndexMap;
use shirabe_php_shim::{
bzcompress, file_exists, file_put_contents, function_exists, gzcompress, pack, str_repeat,
strrpos, unlink, FilesystemIterator, Phar, PharData, PhpMixed, RuntimeException,
};
use crate::package::archiver::archivable_files_filter::ArchivableFilesFilter;
use crate::package::archiver::archivable_files_finder::ArchivableFilesFinder;
use crate::package::archiver::archiver_interface::ArchiverInterface;
fn formats() -> IndexMap<&'static str, i64> {
let mut m = IndexMap::new();
m.insert("zip", Phar::ZIP);
m.insert("tar", Phar::TAR);
m.insert("tar.gz", Phar::TAR);
m.insert("tar.bz2", Phar::TAR);
m
}
fn compress_formats() -> IndexMap<&'static str, i64> {
let mut m = IndexMap::new();
m.insert("tar.gz", Phar::GZ);
m.insert("tar.bz2", Phar::BZ2);
m
}
#[derive(Debug)]
pub struct PharArchiver;
impl ArchiverInterface for PharArchiver {
fn archive(
&self,
sources: String,
target: String,
format: String,
excludes: Vec<String>,
ignore_filters: bool,
) -> anyhow::Result<String> {
let sources = shirabe_php_shim::realpath(&sources).unwrap_or(sources);
let formats = formats();
let compress_formats = compress_formats();
if file_exists(&target) {
unlink(&target);
}
let inner = (|| -> anyhow::Result<String> {
let pos = strrpos(&target, &format).unwrap_or(target.len());
let filename = target[..pos.saturating_sub(1)].to_string();
let target = if compress_formats.contains_key(format.as_str()) {
format!("{}.tar", filename)
} else {
target
};
let phar = PharData::new_with_format(
target.clone(),
FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO,
"",
*formats.get(format.as_str()).unwrap_or(&Phar::TAR),
);
let files = ArchivableFilesFinder::new(&sources, excludes, ignore_filters)?;
let mut files_only = ArchivableFilesFilter::new(files);
phar.build_from_iterator(&mut files_only, &sources);
files_only.add_empty_dir(&phar, &sources);
if !file_exists(&target) {
let target = format!("{}.{}", filename, format);
drop(phar);
if format == "tar" {
// create an empty tar file (=10240 null bytes) if the tar file is empty and PharData thus did not write it to disk
file_put_contents(&target, &str_repeat("\0", 10240).into_bytes());
} else if format == "zip" {
// create minimal valid ZIP file (Empty Central Directory + End of Central Directory record)
let eocd = pack(
"VvvvvVVv",
&[
PhpMixed::Int(0x06054b50), // End of central directory signature
PhpMixed::Int(0), // Number of this disk
PhpMixed::Int(0), // Disk where central directory starts
PhpMixed::Int(0), // Number of central directory records on this disk
PhpMixed::Int(0), // Total number of central directory records
PhpMixed::Int(0), // Size of central directory (bytes)
PhpMixed::Int(0), // Offset of start of central directory
PhpMixed::Int(0), // Comment length
],
);
file_put_contents(&target, &eocd);
} else if format == "tar.gz" || format == "tar.bz2" {
let compress_algo = *compress_formats.get(format.as_str()).unwrap();
if !PharData::can_compress(compress_algo) {
return Err(RuntimeException {
message: format!("Can not compress to {} format", format),
code: 0,
}
.into());
}
if format == "tar.gz" && function_exists("gzcompress") {
let data = gzcompress(&str_repeat("\0", 10240).into_bytes()).unwrap_or_default();
file_put_contents(&target, &data);
} else if format == "tar.bz2" && function_exists("bzcompress") {
let data = bzcompress(&str_repeat("\0", 10240).into_bytes()).unwrap_or_default();
file_put_contents(&target, &data);
}
}
return Ok(target);
}
if compress_formats.contains_key(format.as_str()) {
let compress_algo = *compress_formats.get(format.as_str()).unwrap();
if !PharData::can_compress(compress_algo) {
return Err(RuntimeException {
message: format!("Can not compress to {} format", format),
code: 0,
}
.into());
}
unlink(&target);
phar.compress(compress_algo);
let target = format!("{}.{}", filename, format);
return Ok(target);
}
Ok(target)
})();
inner.map_err(|e| {
let message = format!(
"Could not create archive '{}' from '{}': {}",
target, sources, e
);
anyhow::anyhow!(RuntimeException { message, code: 0 })
})
}
fn supports(&self, format: String, _source_type: Option<String>) -> bool {
formats().contains_key(format.as_str())
}
}
|