Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't assume that file binary exists on *nix OS #26886

Merged
merged 1 commit into from
Apr 17, 2018
Merged

Conversation

teohhanhui
Copy link
Contributor

Q A
Branch? 2.7
Bug fix? yes
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets N/A
License MIT
Doc PR N/A

Certain lightweight distributions such as Alpine Linux (popular for smaller Docker images) do not include it by default.

@@ -43,7 +43,7 @@ public function __construct($cmd = 'file -b --mime %s 2>/dev/null')
*/
public static function isSupported()
{
return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg');
return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg') && null !== shell_exec('command -v file');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the performance penalty for doing this call is high, the result should be cached

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -43,7 +43,7 @@ public function __construct($cmd = 'file -b --mime %s 2>/dev/null')
*/
public static function isSupported()
{
return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg');
return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg') && static::hasFileBinary();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this must be self:: to access a private API. Otherwise, a child class would break when calling this code (as it would try to call ChildClass::hasFileBinary)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just tried this out. It'd only break when ChildClass overrides hasFileBinary. But yes, there is no reason for us to use static:: here, since it's a private method anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could cache the full expression, including the function_exists check (and thus not need any new method btw)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done and done.

{
static $exists;

return isset($exists) ? $exists : ($exists = null !== shell_exec('command -v file'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should use passthru as well to run the command. Otherwise, it requires adding shell_exec to the list of existing functions (as these are functions which are likely to get disabled on some hosting)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@teohhanhui
Copy link
Contributor Author

Status: Needs Review

@@ -43,7 +43,33 @@ public function __construct($cmd = 'file -b --mime %s 2>/dev/null')
*/
public static function isSupported()
{
return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg');
static $supported;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't mind, here is an equivalent shorter implementation:

+        static $supported = null;
+
+        if (null !== $supported) {
+            return $supported;
+        }
+
+        if ('\\' === DIRECTORY_SEPARATOR || !function_exists('passthru') || !function_exists('escapeshellarg')) {
+            return $supported = false;
+        }
+
+        ob_start();
+        passthru('command -v file', $return);
+        $binPath = trim(ob_get_clean());
+
+        return $supported = 0 === $return && preg_match('#^/[^\0/]#', $binPath);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But not as readable...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less cyclomatic complexity so easier to understand actually to me...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cyclomatic complexity might not be a good measure for readability: https://web.eecs.umich.edu/~weimerw/p/weimer-tse2010-readability-preprint.pdf


$binPath = trim(ob_get_clean());

if (!preg_match('#^(?:/[^\0/])+#', $binPath)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this specific regex? can the NUL char actually happen?
also, it looks like this could be just '#^/[^\0/]#'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's possible to have a NUL character:

ob_start();
passthru('find -maxdepth 1 -print0');
$output = trim(ob_get_clean());
echo ord($output[1]);

gives:

0

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Apr 13, 2018

Yes, it's possible to have a NUL character

But your example doesn't use command -v, so I'm not sure it applies, does it?

About the proposal I made, I did it because I found it harder to review when ob_* closing functions are called in two different code paths. I made the proposal to make Symfony better, not to bother you.

@teohhanhui
Copy link
Contributor Author

But your example doesn't use command -v, so I'm not sure it applies, does it?

Rather not assume that command -v will return what we expect it to return. It doesn't complicate the regex much anyway...

About the proposal I made, I did it because I found it harder to review when ob_* closing functions are called in two different code paths. I made the proposal to make Symfony better, not to bother you.

Got it. I will make the change.

@teohhanhui
Copy link
Contributor Author

Removed the preg_match as it's just unnecessary (and what if file is a shell built-in lol)

return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg');
static $supported;

if (isset($supported)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use if (null !== $supported) here instead.

Certain lightweight distributions such as Alpine Linux (popular for smaller Docker images) do not include it by default.
@teohhanhui
Copy link
Contributor Author

Status: Needs Review

@fabpot
Copy link
Member

fabpot commented Apr 17, 2018

Thank you @teohhanhui.

@fabpot fabpot merged commit e2c1f24 into symfony:2.7 Apr 17, 2018
fabpot added a commit that referenced this pull request Apr 17, 2018
This PR was merged into the 2.7 branch.

Discussion
----------

Don't assume that file binary exists on *nix OS

| Q             | A
| ------------- | ---
| Branch?       | 2.7
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | N/A
| License       | MIT
| Doc PR        | N/A

Certain lightweight distributions such as Alpine Linux (popular for smaller Docker images) do not include it by default.

Commits
-------

e2c1f24 Don't assume that file binary exists on *nix OS
fabpot added a commit that referenced this pull request May 25, 2018
…ialization (nicolas-grekas)

This PR was merged into the 2.7 branch.

Discussion
----------

[HttpFoundation] Fix perf issue during MimeTypeGuesser intialization

| Q             | A
| ------------- | ---
| Branch?       | 2.7
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #27307
| License       | MIT
| Doc PR        | -

introduced in #26886

![image](https://user-images.githubusercontent.com/243674/40451947-918f5358-5ee0-11e8-9f1a-cf707bf3cefa.png)

Commits
-------

f8e7a18 [HttpFoundation] Fix perf issue during MimeTypeGuesser intialization
@teohhanhui teohhanhui deleted the patch-3 branch March 12, 2019 14:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants