aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-23 12:43:10 +0900
committernsfisis <nsfisis@gmail.com>2026-08-23 13:24:29 +0900
commit0b48f4a46d24248e4c012ef37d25b5963c27a78c (patch)
treea4c189be4419ded8e2252bc0c250aecfc66b3c36 /crates/shirabe
parent890a50de2a740cc4c4edba12b37cc9aa4d0efdc5 (diff)
downloadphp-shirabe-0b48f4a46d24248e4c012ef37d25b5963c27a78c.tar.gz
php-shirabe-0b48f4a46d24248e4c012ef37d25b5963c27a78c.tar.zst
php-shirabe-0b48f4a46d24248e4c012ef37d25b5963c27a78c.zip
fix(fs): carry file_get_contents results as bytes
file_get_contents() and file_get_contents_with_max_length() return Vec<u8> instead of a from_utf8_lossy'd String. Call sites whose consumer takes a &str still convert lossily and are marked TODO(bytes). file_get_contents_with_max_length() now reads at most the requested number of bytes instead of the whole file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe')
-rw-r--r--crates/shirabe/src/autoload/autoload_generator.rs7
-rw-r--r--crates/shirabe/src/cache.rs6
-rw-r--r--crates/shirabe/src/command/bump_command.rs11
-rw-r--r--crates/shirabe/src/command/init_command.rs4
-rw-r--r--crates/shirabe/src/command/package_discovery_trait.rs4
-rw-r--r--crates/shirabe/src/command/require_command.rs23
-rw-r--r--crates/shirabe/src/config/json_config_source.rs6
-rw-r--r--crates/shirabe/src/console/application.rs10
-rw-r--r--crates/shirabe/src/downloader/zip_downloader.rs19
-rw-r--r--crates/shirabe/src/factory.rs6
-rw-r--r--crates/shirabe/src/installer/binary_installer.rs14
-rw-r--r--crates/shirabe/src/json/json_file.rs14
-rw-r--r--crates/shirabe/src/package/locker.rs8
-rw-r--r--crates/shirabe/src/plugin/plugin_manager.rs11
-rw-r--r--crates/shirabe/src/repository/path_repository.rs6
-rw-r--r--crates/shirabe/src/util/config_validator.rs6
-rw-r--r--crates/shirabe/src/util/filesystem.rs4
-rw-r--r--crates/shirabe/src/util/http_downloader.rs2
-rw-r--r--crates/shirabe/src/util/perforce.rs3
-rw-r--r--crates/shirabe/src/util/platform.rs12
-rw-r--r--crates/shirabe/src/util/remote_filesystem.rs27
-rw-r--r--crates/shirabe/tests/dependency_resolver/pool_builder_test.rs4
-rw-r--r--crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs4
-rw-r--r--crates/shirabe/tests/repository/path_repository_test.rs6
-rw-r--r--crates/shirabe/tests/util/remote_filesystem_test.rs9
25 files changed, 148 insertions, 78 deletions
diff --git a/crates/shirabe/src/autoload/autoload_generator.rs b/crates/shirabe/src/autoload/autoload_generator.rs
index 55124467..3abf3453 100644
--- a/crates/shirabe/src/autoload/autoload_generator.rs
+++ b/crates/shirabe/src/autoload/autoload_generator.rs
@@ -556,8 +556,11 @@ return array(
// carry over existing autoload.php's suffix if possible and none is configured
if suffix.is_none() && Filesystem::is_readable(&format!("{}/autoload.php", vendor_path))
{
- let content =
- file_get_contents(format!("{}/autoload.php", vendor_path)).unwrap_or_default();
+ // TODO(bytes): preg_match matches over a &str.
+ let content = String::from_utf8_lossy(
+ &file_get_contents(format!("{}/autoload.php", vendor_path)).unwrap_or_default(),
+ )
+ .into_owned();
if let Some(matches) =
preg_match(php_regex!("{ComposerAutoloaderInit([^:\\s]+)::}"), &content)
{
diff --git a/crates/shirabe/src/cache.rs b/crates/shirabe/src/cache.rs
index a8e4fb26..269a31ef 100644
--- a/crates/shirabe/src/cache.rs
+++ b/crates/shirabe/src/cache.rs
@@ -136,7 +136,11 @@ impl Cache {
crate::io::DEBUG,
);
- return file_get_contents(&full_path);
+ // TODO(bytes): the payload is handed back to callers as a String, so a cache
+ // entry that is not valid UTF-8 is corrupted by from_utf8_lossy.
+ return file_get_contents(&full_path)
+ .map(|c| String::from_utf8_lossy(&c).into_owned())
+ .ok();
}
}
diff --git a/crates/shirabe/src/command/bump_command.rs b/crates/shirabe/src/command/bump_command.rs
index e10f7b15..9639cbe9 100644
--- a/crates/shirabe/src/command/bump_command.rs
+++ b/crates/shirabe/src/command/bump_command.rs
@@ -73,8 +73,8 @@ impl BumpCommand {
let composer_json = JsonFile::new(composer_json_path.clone(), None, None)?;
let contents = match file_get_contents(composer_json.get_path()) {
- Some(c) => c,
- None => {
+ Ok(c) => c,
+ Err(_) => {
io.write_error3(
&format!("<error>{} is not readable.</error>", composer_json_path),
true,
@@ -86,7 +86,7 @@ impl BumpCommand {
if !is_writable(&composer_json_path)
&& Silencer::call(|| {
- file_put_contents(&composer_json_path, contents.as_bytes())
+ file_put_contents(&composer_json_path, &contents)
.map(|_| ())
.ok_or_else(|| anyhow::anyhow!("file_put_contents failed"))
})
@@ -305,8 +305,9 @@ impl BumpCommand {
updates: &indexmap::IndexMap<&str, indexmap::IndexMap<String, String>>,
) -> anyhow::Result<bool> {
let contents = match file_get_contents(json.get_path()) {
- Some(c) => c,
- None => {
+ // TODO(bytes): JsonManipulator takes the JSON as a String.
+ Ok(c) => String::from_utf8_lossy(&c).into_owned(),
+ Err(_) => {
return Err(shirabe_php_shim::RuntimeException::new(format!(
"Unable to read {} contents.",
json.get_path()
diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs
index 8d892bd3..67530008 100644
--- a/crates/shirabe/src/command/init_command.rs
+++ b/crates/shirabe/src/command/init_command.rs
@@ -215,7 +215,9 @@ impl InitCommand {
fn add_vendor_ignore(&self, ignore_file: &str, vendor: &str) {
let mut contents = String::new();
if file_exists(ignore_file) {
- contents = file_get_contents(ignore_file).unwrap_or_default();
+ // TODO(bytes): the ignore file is edited as a String.
+ contents = String::from_utf8_lossy(&file_get_contents(ignore_file).unwrap_or_default())
+ .into_owned();
if strpos(&contents, "\n") != Some(0) {
contents.push('\n');
diff --git a/crates/shirabe/src/command/package_discovery_trait.rs b/crates/shirabe/src/command/package_discovery_trait.rs
index 4a915fbe..4816e9d4 100644
--- a/crates/shirabe/src/command/package_discovery_trait.rs
+++ b/crates/shirabe/src/command/package_discovery_trait.rs
@@ -112,7 +112,9 @@ pub trait PackageDiscoveryTrait: BaseCommand {
// @phpstan-ignore-next-line as RequireCommand does not have the option above so this code is reachable there
let file = Factory::get_composer_file().unwrap_or_default();
if is_file(&file) && Filesystem::is_readable(&file) {
- let contents = file_get_contents(&file).unwrap_or_default();
+ // TODO(bytes): json_decode_assoc takes the JSON as a &str.
+ let contents =
+ String::from_utf8_lossy(&file_get_contents(&file).unwrap_or_default()).into_owned();
let composer = json_decode_assoc(&contents).unwrap_or(PhpMixed::Null);
if is_array(&composer)
&& let Some(arr) = composer.as_array()
diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs
index 4f90a532..2dfb140d 100644
--- a/crates/shirabe/src/command/require_command.rs
+++ b/crates/shirabe/src/command/require_command.rs
@@ -51,11 +51,11 @@ pub struct RequireCommand {
first_require: std::cell::Cell<bool>,
json: std::cell::RefCell<Option<std::rc::Rc<std::cell::RefCell<JsonFile>>>>,
file: std::cell::RefCell<String>,
- composer_backup: std::cell::RefCell<String>,
+ composer_backup: std::cell::RefCell<Vec<u8>>,
/// file name
lock: std::cell::RefCell<String>,
/// contents before modification if the lock file exists
- lock_backup: std::cell::RefCell<Option<String>>,
+ lock_backup: std::cell::RefCell<Option<Vec<u8>>>,
dependency_resolution_completed: std::rc::Rc<std::cell::Cell<bool>>,
repos: std::cell::RefCell<Option<crate::repository::RepositoryInterfaceHandle>>,
repository_sets:
@@ -78,7 +78,7 @@ impl RequireCommand {
first_require: std::cell::Cell::new(false),
json: std::cell::RefCell::new(None),
file: std::cell::RefCell::new(String::new()),
- composer_backup: std::cell::RefCell::new(String::new()),
+ composer_backup: std::cell::RefCell::new(Vec::new()),
lock: std::cell::RefCell::new(String::new()),
lock_backup: std::cell::RefCell::new(None),
dependency_resolution_completed: std::rc::Rc::new(std::cell::Cell::new(false)),
@@ -655,7 +655,11 @@ impl RequireCommand {
remove_key: &str,
sort_packages: bool,
) -> bool {
- let contents = file_get_contents(json.borrow().get_path()).unwrap_or_default();
+ // TODO(bytes): JsonManipulator takes the JSON as a String.
+ let contents = String::from_utf8_lossy(
+ &file_get_contents(json.borrow().get_path()).unwrap_or_default(),
+ )
+ .into_owned();
let mut manipulator = match JsonManipulator::new(contents) {
Ok(m) => m,
@@ -712,12 +716,9 @@ impl RequireCommand {
extra
);
self.get_io().write_error3(&msg, true, io_interface::NORMAL);
- file_put_contents(
- json.borrow().get_path(),
- self.composer_backup.borrow().as_bytes(),
- );
+ file_put_contents(json.borrow().get_path(), &self.composer_backup.borrow());
if let Some(ref lock_backup) = *self.lock_backup.borrow() {
- file_put_contents(&lock, lock_backup.as_bytes());
+ file_put_contents(&lock, lock_backup);
}
}
}
@@ -837,7 +838,7 @@ impl Command for RequireCommand {
file_get_contents(json.borrow().get_path()).unwrap_or_default();
let lock = self.lock.borrow().clone();
*self.lock_backup.borrow_mut() = if file_exists(&lock) {
- file_get_contents(&lock)
+ file_get_contents(&lock).ok()
} else {
None
};
@@ -859,7 +860,7 @@ impl Command for RequireCommand {
let backup_contents = self.composer_backup.borrow().clone();
if !is_writable(&file)
&& Silencer::call(|| {
- shirabe_php_shim::file_put_contents(&file_path, backup_contents.as_bytes());
+ shirabe_php_shim::file_put_contents(&file_path, &backup_contents);
Ok::<bool, anyhow::Error>(false)
})
.ok()
diff --git a/crates/shirabe/src/config/json_config_source.rs b/crates/shirabe/src/config/json_config_source.rs
index 59146630..5e927398 100644
--- a/crates/shirabe/src/config/json_config_source.rs
+++ b/crates/shirabe/src/config/json_config_source.rs
@@ -48,7 +48,11 @@ impl JsonConfigSource {
.into());
}
- contents = file_get_contents(self.file.borrow().get_path()).unwrap_or_default();
+ // TODO(bytes): JsonManipulator takes the JSON as a String.
+ contents = String::from_utf8_lossy(
+ &file_get_contents(self.file.borrow().get_path()).unwrap_or_default(),
+ )
+ .into_owned();
} else if self.auth_config {
contents = "{\n}\n".to_string();
} else {
diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs
index 24aee249..233ff751 100644
--- a/crates/shirabe/src/console/application.rs
+++ b/crates/shirabe/src/console/application.rs
@@ -2263,7 +2263,7 @@ impl ApplicationHandle {
bin2hex(&random_bytes(5))
);
if !(file_put_contents(&tempfile, file!().as_bytes()).is_some_and(|n| n > 0)
- && file_get_contents(&tempfile).as_deref() == Some(file!())
+ && file_get_contents(&tempfile).as_deref().ok() == Some(file!().as_bytes())
&& unlink(&tempfile).is_ok()
&& !file_exists(&tempfile))
{
@@ -2280,9 +2280,11 @@ impl ApplicationHandle {
// add non-standard scripts as own commands
let file = Factory::get_composer_file().unwrap_or_default();
if may_need_script_command && is_file(&file) && Filesystem::is_readable(&file) {
- let composer_json: PhpMixed =
- json_decode_assoc(&file_get_contents(&file).unwrap_or_default())
- .unwrap_or(PhpMixed::Null);
+ // TODO(bytes): json_decode_assoc takes the JSON as a &str.
+ let composer_json: PhpMixed = json_decode_assoc(&String::from_utf8_lossy(
+ &file_get_contents(&file).unwrap_or_default(),
+ ))
+ .unwrap_or(PhpMixed::Null);
if let Some(arr) = composer_json.as_array()
&& let Some(scripts) = arr.get("scripts").and_then(|v| v.as_array())
{
diff --git a/crates/shirabe/src/downloader/zip_downloader.rs b/crates/shirabe/src/downloader/zip_downloader.rs
index cdb9307c..c0470162 100644
--- a/crates/shirabe/src/downloader/zip_downloader.rs
+++ b/crates/shirabe/src/downloader/zip_downloader.rs
@@ -12,7 +12,7 @@ use shirabe_php_shim::Catch as _;
use shirabe_php_shim::{
CmpOp, ErrorException, PhpMixed, RuntimeException, UnexpectedValueException, ZipArchive,
bin2hex, class_exists, file_exists, file_get_contents, filesize, hash_file, impl_php_class,
- is_file, json_encode, php_regex, preg_match, random_int, str_replace, strlen, substr,
+ is_file, json_encode, php_regex, preg_match, random_int, str_replace, strlen, substr_bytes,
version_compare,
};
use shirabe_symfony_process::ExecutableFinder;
@@ -233,16 +233,19 @@ impl ZipDownloader {
));
self.inner.io.borrow().write_error(&format!(
"First 100 bytes (hex): {}",
- bin2hex(
- substr(&file_get_contents(file).unwrap_or_default(), 0, Some(100))
- .as_bytes()
- )
+ bin2hex(&substr_bytes(
+ &file_get_contents(file).unwrap_or_default(),
+ 0,
+ Some(100)
+ ))
));
self.inner.io.borrow().write_error(&format!(
"Last 100 bytes (hex): {}",
- bin2hex(
- substr(&file_get_contents(file).unwrap_or_default(), -100, None).as_bytes()
- )
+ bin2hex(&substr_bytes(
+ &file_get_contents(file).unwrap_or_default(),
+ -100,
+ None
+ ))
));
if strlen(&package.get_dist_url().unwrap_or_default()) > 0 {
self.inner.io.borrow().write_error(&format!(
diff --git a/crates/shirabe/src/factory.rs b/crates/shirabe/src/factory.rs
index afdedd81..6889a20d 100644
--- a/crates/shirabe/src/factory.rs
+++ b/crates/shirabe/src/factory.rs
@@ -766,7 +766,11 @@ impl Factory {
Some(io.clone()),
)?,
im,
- &file_get_contents(composer_file_path).unwrap_or_default(),
+ // TODO(bytes): Locker takes the composer.json contents as a &str
+ // to hash them.
+ &String::from_utf8_lossy(
+ &file_get_contents(composer_file_path).unwrap_or_default(),
+ ),
process,
);
composer_full
diff --git a/crates/shirabe/src/installer/binary_installer.rs b/crates/shirabe/src/installer/binary_installer.rs
index 554620df..1f70a5ca 100644
--- a/crates/shirabe/src/installer/binary_installer.rs
+++ b/crates/shirabe/src/installer/binary_installer.rs
@@ -9,9 +9,9 @@ use crate::util::Platform;
use crate::util::ProcessExecutor;
use crate::util::Silencer;
use shirabe_php_shim::{
- PhpMixed, basename, basename_with_suffix, chmod, dirname, fclose, fgets, file_exists,
- file_get_contents5, file_put_contents, fopen, is_dir, is_file, is_link, php_regex, preg_match,
- realpath, rmdir, substr, trim, umask,
+ basename, basename_with_suffix, chmod, dirname, fclose, fgets, file_exists,
+ file_get_contents_with_max_length, file_put_contents, fopen, is_dir, is_file, is_link,
+ php_regex, preg_match, realpath, rmdir, substr, trim, umask,
};
/// Seam over the BinaryInstaller methods reached through LibraryInstaller, so tests can inject a
@@ -311,8 +311,12 @@ impl BinaryInstaller {
let bin_dir = ProcessExecutor::escape(&dirname(&bin_path));
let bin_file = basename(&bin_path);
- let bin_contents =
- file_get_contents5(bin, false, PhpMixed::Null, 0, Some(500)).unwrap_or_default();
+ // TODO(bytes): preg_match matches over a &str, and the shebang it captures is spliced
+ // into the generated proxy as a String.
+ let bin_contents = String::from_utf8_lossy(
+ &file_get_contents_with_max_length(bin, 500).unwrap_or_default(),
+ )
+ .into_owned();
// For php files, we generate a PHP proxy instead of a shell one,
// which allows calling the proxy with a custom php process
if let Some(m) = preg_match(
diff --git a/crates/shirabe/src/json/json_file.rs b/crates/shirabe/src/json/json_file.rs
index c4a06d77..703b9fbb 100644
--- a/crates/shirabe/src/json/json_file.rs
+++ b/crates/shirabe/src/json/json_file.rs
@@ -165,7 +165,11 @@ impl JsonFile {
io_interface::NORMAL,
);
}
- Ok(file_get_contents(&self.path))
+ // TODO(bytes): the JSON text travels as a String, as it does on the
+ // HttpDownloader branch above.
+ Ok(file_get_contents(&self.path)
+ .map(|c| String::from_utf8_lossy(&c).into_owned())
+ .ok())
}
})() {
Ok(j) => j,
@@ -282,10 +286,10 @@ impl JsonFile {
content: &str,
) -> anyhow::Result<Option<i64>> {
// PHP: @file_get_contents($path)
- let current_content = Silencer::call(|| Ok(file_get_contents(path)))
+ let current_content = Silencer::call(|| Ok(file_get_contents(path).ok()))
.ok()
.flatten();
- if current_content.is_none() || current_content.as_deref() != Some(content) {
+ if current_content.is_none() || current_content.as_deref() != Some(content.as_bytes()) {
return Ok(file_put_contents(path, content.as_bytes()));
}
@@ -307,7 +311,9 @@ impl JsonFile {
))
.into());
}
- let content = file_get_contents(&self.path).unwrap_or_default();
+ // TODO(bytes): json_decode_obj and validate_syntax take the JSON as a &str.
+ let content = String::from_utf8_lossy(&file_get_contents(&self.path).unwrap_or_default())
+ .into_owned();
let data = json_decode_obj(&content)?;
if matches!(data, PhpMixed::Null) && content != "null" {
diff --git a/crates/shirabe/src/package/locker.rs b/crates/shirabe/src/package/locker.rs
index b98b8dae..3af06ce3 100644
--- a/crates/shirabe/src/package/locker.rs
+++ b/crates/shirabe/src/package/locker.rs
@@ -631,10 +631,10 @@ impl Locker {
Box<dyn FnOnce(IndexMap<String, PhpMixed>) -> IndexMap<String, PhpMixed>>,
>,
) -> anyhow::Result<()> {
- let contents = file_get_contents(composer_json.get_path());
- let contents = match contents {
- Some(s) => s,
- None => {
+ let contents = match file_get_contents(composer_json.get_path()) {
+ // TODO(bytes): get_content_hash parses the contents as a &str.
+ Ok(s) => String::from_utf8_lossy(&s).into_owned(),
+ Err(_) => {
return Err(RuntimeException::new(format!(
"Unable to read {} contents to update the lock file hash.",
composer_json.get_path()
diff --git a/crates/shirabe/src/plugin/plugin_manager.rs b/crates/shirabe/src/plugin/plugin_manager.rs
index 04c53d08..bc434262 100644
--- a/crates/shirabe/src/plugin/plugin_manager.rs
+++ b/crates/shirabe/src/plugin/plugin_manager.rs
@@ -434,10 +434,13 @@ impl PluginManager {
let path = class_loader.find_file(&class).unwrap_or_else(|| {
panic!("plugin class `{class}` is already defined but has no autoloadable file")
});
- // TODO(bytes): file_get_contents is lossy UTF-8; the eval'd plugin source
- // should be carried as bytes.
- let code = file_get_contents(&path)
- .unwrap_or_else(|| panic!("unable to read the plugin class file `{path}`"));
+ // TODO(bytes): the eval'd plugin source is carried as a String, so
+ // from_utf8_lossy corrupts a source file that is not valid UTF-8.
+ let code =
+ String::from_utf8_lossy(&file_get_contents(&path).unwrap_or_else(|_| {
+ panic!("unable to read the plugin class file `{path}`")
+ }))
+ .into_owned();
let class_counter = CLASS_COUNTER.load(std::sync::atomic::Ordering::Relaxed);
let separator_pos = strrpos(&class, "\\");
let mut class_name = class.clone();
diff --git a/crates/shirabe/src/repository/path_repository.rs b/crates/shirabe/src/repository/path_repository.rs
index 17f22d40..25a5ad57 100644
--- a/crates/shirabe/src/repository/path_repository.rs
+++ b/crates/shirabe/src/repository/path_repository.rs
@@ -184,7 +184,11 @@ impl PathRepository {
continue;
}
- let json = file_get_contents(&composer_file_path).unwrap_or_default();
+ // TODO(bytes): JsonFile::parse_json takes the JSON as a &str.
+ let json = String::from_utf8_lossy(
+ &file_get_contents(&composer_file_path).unwrap_or_default(),
+ )
+ .into_owned();
let parsed = JsonFile::parse_json(Some(&json), Some(&composer_file_path))?;
let mut package: IndexMap<String, PhpMixed> = match parsed {
PhpMixed::Array(m) => m.into_iter().collect(),
diff --git a/crates/shirabe/src/util/config_validator.rs b/crates/shirabe/src/util/config_validator.rs
index f8018fe5..93d3b4bb 100644
--- a/crates/shirabe/src/util/config_validator.rs
+++ b/crates/shirabe/src/util/config_validator.rs
@@ -72,7 +72,11 @@ impl ConfigValidator {
}
if manifest.is_some() {
- let contents = shirabe_php_shim::file_get_contents(file).unwrap_or_default();
+ // TODO(bytes): detect_duplicate_keys scans the JSON as a &str.
+ let contents = String::from_utf8_lossy(
+ &shirabe_php_shim::file_get_contents(file).unwrap_or_default(),
+ )
+ .into_owned();
if let Some((key, line)) = detect_duplicate_keys(&contents) {
warnings.push(format!("Key {key} is a duplicate in {file} at line {line}"));
}
diff --git a/crates/shirabe/src/util/filesystem.rs b/crates/shirabe/src/util/filesystem.rs
index 05146fd3..8408a8a9 100644
--- a/crates/shirabe/src/util/filesystem.rs
+++ b/crates/shirabe/src/util/filesystem.rs
@@ -804,7 +804,7 @@ impl Filesystem {
}
if is_file(path) {
- return Silencer::call(|| Ok(file_get_contents(path).is_some())).unwrap_or(false);
+ return Silencer::call(|| Ok(file_get_contents(path).is_ok())).unwrap_or(false);
}
if is_dir(path) {
@@ -1018,7 +1018,7 @@ impl Filesystem {
pub fn file_put_contents_if_modified(&self, path: &str, content: &str) -> anyhow::Result<i64> {
let current_content =
Silencer::call(|| Ok(file_get_contents(path).unwrap_or_default())).unwrap_or_default();
- if current_content.is_empty() || current_content != content {
+ if current_content.is_empty() || current_content != content.as_bytes() {
return Ok(file_put_contents(path, content.as_bytes()).unwrap_or(0));
}
diff --git a/crates/shirabe/src/util/http_downloader.rs b/crates/shirabe/src/util/http_downloader.rs
index 8d34dcdf..f9f69c9b 100644
--- a/crates/shirabe/src/util/http_downloader.rs
+++ b/crates/shirabe/src/util/http_downloader.rs
@@ -466,7 +466,7 @@ impl HttpDownloader {
let _ = stream_context_create(&ctx_options, None);
let test_connectivity = file_get_contents("https://8.8.8.8");
Silencer::restore();
- if test_connectivity.is_some() {
+ if test_connectivity.is_ok() {
return Some(vec![
"<error>The following exception probably indicates you have misconfigured DNS resolver(s)</error>".to_string(),
]);
diff --git a/crates/shirabe/src/util/perforce.rs b/crates/shirabe/src/util/perforce.rs
index 295851c6..3804eed7 100644
--- a/crates/shirabe/src/util/perforce.rs
+++ b/crates/shirabe/src/util/perforce.rs
@@ -371,8 +371,9 @@ impl Perforce {
p4_create_client_command,
None,
None,
+ // TODO(bytes): Process carries its stdin as a PhpMixed::String.
file_get_contents(self.get_p4_client_spec())
- .map(PhpMixed::String)
+ .map(|s| PhpMixed::String(String::from_utf8_lossy(&s).into_owned()))
.unwrap_or(PhpMixed::Null),
None,
)?;
diff --git a/crates/shirabe/src/util/platform.rs b/crates/shirabe/src/util/platform.rs
index ad530db3..bb3dd739 100644
--- a/crates/shirabe/src/util/platform.rs
+++ b/crates/shirabe/src/util/platform.rs
@@ -164,13 +164,14 @@ impl Platform {
return false;
}
- let file_contents = Silencer::call(|| Ok(file_get_contents("/proc/version")))
+ let file_contents = Silencer::call(|| Ok(file_get_contents("/proc/version").ok()))
.ok()
.flatten()
.unwrap_or_default();
if !ini_get("open_basedir").is_some_and(|s| PhpMixed::String(s).to_bool())
&& is_readable("/proc/version")
- && stripos(&file_contents, "microsoft").is_some()
+ // TODO(bytes)
+ && stripos(&String::from_utf8_lossy(&file_contents), "microsoft").is_some()
&& !Self::is_docker()
// Docker and Podman running inside WSL should not be seen as WSL
{
@@ -224,11 +225,12 @@ impl Platform {
Err(_) => break,
};
let data = match data {
- Some(d) => d,
- None => continue,
+ Ok(d) => d,
+ Err(_) => continue,
};
// detect default mount points created by Docker/containerd
- if data.contains("/var/lib/docker/") || data.contains("/io.containerd.snapshotter") {
+ let contains = |needle: &[u8]| data.windows(needle.len()).any(|w| w == needle);
+ if contains(b"/var/lib/docker/") || contains(b"/io.containerd.snapshotter") {
*cached = Some(true);
return true;
}
diff --git a/crates/shirabe/src/util/remote_filesystem.rs b/crates/shirabe/src/util/remote_filesystem.rs
index b584d722..85a48808 100644
--- a/crates/shirabe/src/util/remote_filesystem.rs
+++ b/crates/shirabe/src/util/remote_filesystem.rs
@@ -17,10 +17,10 @@ use shirabe_php_shim::Catch as _;
use shirabe_php_shim::{
PhpMixed, RuntimeException, STREAM_NOTIFY_FAILURE, STREAM_NOTIFY_FILE_SIZE_IS,
STREAM_NOTIFY_PROGRESS, array_replace_recursive, base64_encode, explode, extension_loaded,
- file_get_contents, file_get_contents5, file_put_contents, filter_var_boolean, gethostbyname,
- http_clear_last_response_headers, http_get_last_response_headers, ini_get, json_decode_assoc,
- parse_url, php_regex, preg_is_match, preg_match, preg_quote, preg_replace, strpos, strtolower,
- strtr, substr, trim, zlib_decode,
+ file_get_contents, file_get_contents_with_max_length, file_put_contents, filter_var_boolean,
+ gethostbyname, http_clear_last_response_headers, http_get_last_response_headers, ini_get,
+ json_decode_assoc, parse_url, php_regex, preg_is_match, preg_match, preg_quote, preg_replace,
+ strpos, strtolower, strtr, substr, trim, zlib_decode,
};
/// Result of `RemoteFilesystem::get` — string content, `true` (for copy), or `false`.
@@ -715,7 +715,7 @@ impl RemoteFilesystem {
response_headers: &mut Vec<String>,
max_file_size: Option<i64>,
) -> anyhow::Result<Option<String>> {
- let mut result: Option<String> = None;
+ let mut result: Option<Vec<u8>> = None;
// PHP reads the magic `$http_response_header` variable instead before 8.4, which is where
// http_get_last_response_headers() and its companion appeared.
@@ -725,12 +725,13 @@ impl RemoteFilesystem {
// PHP has no scheme branch here: `file_get_contents` reads `file://` URLs and plain
// (scheme-less) local paths through the same stream wrapper it uses for the network
// schemes. Only the local subset is modeled so far.
- let outer: Result<Option<String>, anyhow::Error> =
+ let outer: Result<Option<Vec<u8>>, anyhow::Error> =
if self.scheme == "file" || self.scheme.is_empty() {
Ok(match max_file_size {
- Some(max) => file_get_contents5(file_url, false, PhpMixed::Null, 0, Some(max)),
+ Some(max) => file_get_contents_with_max_length(file_url, max as usize),
None => file_get_contents(file_url),
- })
+ }
+ .ok())
} else {
// TODO(http): wrap PHP's `file_get_contents` with stream context and error capture
// for http(s) and other network schemes; depends on the unmodeled PHP stream-context
@@ -742,13 +743,15 @@ impl RemoteFilesystem {
Err(e) => caught_e = Some(e),
}
+ // Platform::strlen counts bytes whichever branch it takes, so the length is read off the
+ // buffer directly.
if let Some(ref r) = result
&& let Some(max) = max_file_size
- && Platform::strlen(r) >= max
+ && r.len() as i64 >= max
{
return Err(MaxFileSizeExceededException::new(format!(
"Maximum allowed download size reached. Downloaded {} of allowed {} bytes",
- Platform::strlen(r),
+ r.len(),
max
))
.into());
@@ -761,7 +764,9 @@ impl RemoteFilesystem {
return Err(e);
}
- Ok(result)
+ // TODO(bytes): the body is handed back as a String because RemoteFilesystem::get and
+ // GetResult carry it as one; from_utf8_lossy corrupts binary payloads.
+ Ok(result.map(|r| String::from_utf8_lossy(&r).into_owned()))
}
fn callback_get(
diff --git a/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs b/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs
index a9b59596..50c97366 100644
--- a/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs
+++ b/crates/shirabe/tests/dependency_resolver/pool_builder_test.rs
@@ -54,7 +54,9 @@ fn load_package(
}
fn read_test_file(file: &str, fixtures_dir: &str) -> IndexMap<String, String> {
- let contents = shirabe_php_shim::file_get_contents(file).unwrap();
+ // TODO(bytes)
+ let contents =
+ String::from_utf8_lossy(&shirabe_php_shim::file_get_contents(file).unwrap()).into_owned();
let tokens = preg_split_delim_capture(php_regex!(r"#(?:^|\n*)--([A-Z-]+)--\n#"), &contents);
// PHP section_info is a map of name => required flag.
diff --git a/crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs b/crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs
index b84d152e..928119f3 100644
--- a/crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs
+++ b/crates/shirabe/tests/dependency_resolver/pool_optimizer_test.rs
@@ -58,7 +58,9 @@ fn reduce_packages_info_for_comparison(packages: &[BasePackageHandle]) -> Vec<St
}
fn read_test_file(file: &str, fixtures_dir: &str) -> IndexMap<String, String> {
- let contents = shirabe_php_shim::file_get_contents(file).unwrap();
+ // TODO(bytes)
+ let contents =
+ String::from_utf8_lossy(&shirabe_php_shim::file_get_contents(file).unwrap()).into_owned();
let tokens = preg_split_delim_capture(php_regex!(r"#(?:^|\n*)--([A-Z-]+)--\n#"), &contents);
let section_info: Vec<&str> = vec!["TEST", "REQUEST", "POOL-BEFORE", "POOL-AFTER"];
diff --git a/crates/shirabe/tests/repository/path_repository_test.rs b/crates/shirabe/tests/repository/path_repository_test.rs
index c892ddc7..9d56ec10 100644
--- a/crates/shirabe/tests/repository/path_repository_test.rs
+++ b/crates/shirabe/tests/repository/path_repository_test.rs
@@ -295,7 +295,11 @@ fn test_reference_config() {
"sha1",
&format!(
"{}{}",
- file_get_contents(format!("{}/composer.json", dist_url)).unwrap_or_default(),
+ // TODO(bytes)
+ String::from_utf8_lossy(
+ &file_get_contents(format!("{}/composer.json", dist_url))
+ .unwrap_or_default()
+ ),
serialize(&PhpMixed::Array(options.clone()))
)
))
diff --git a/crates/shirabe/tests/util/remote_filesystem_test.rs b/crates/shirabe/tests/util/remote_filesystem_test.rs
index d294c5d2..4421f044 100644
--- a/crates/shirabe/tests/util/remote_filesystem_test.rs
+++ b/crates/shirabe/tests/util/remote_filesystem_test.rs
@@ -267,7 +267,14 @@ fn test_copy() {
GetResult::True
));
assert!(std::path::Path::new(&file).exists());
- assert!(strpos(&file_get_contents(&file).unwrap_or_default(), "testCopy").is_some());
+ // TODO(bytes)
+ assert!(
+ strpos(
+ &String::from_utf8_lossy(&file_get_contents(&file).unwrap_or_default()),
+ "testCopy"
+ )
+ .is_some()
+ );
unlink(&file);
}