0

When writing lines of code we sometimes need to consider escaping some characters.

I have come across a situation that I cannot answer on my own

In PHP, the exec command requires a string enclosed in quotes ('). We want to call the command /bin/bash -c which also requires a string enclosed in quotes (')

The solution to this problem is as follows:

$line = exec('/bin/bash -c \'read -e -p "Check to confirm string: " -i "'.$preFill.'" input; echo $input\'');

There are no problems as long as you do not want to insert a character (') in the text proposed to the user. We can see some example using php -a for interactive mode:

php > $preFill = 'I m reach';
php > $line = exec('/bin/bash -c \'read -e -p "Check to confirm string: " -i "'.$preFill.'" input; echo $input\'');
Check to confirm string: I m reach
php >

Question is: which escaping rules I have to consider to put in the right way 'I'm reach' into $preFill?

  • Spawning a bash process seems like a rather roundabout way to read input. – Kusalananda Aug 17 '22 at 12:00
  • True, but bash offers a rather effective solution for proposing a pre-filled response to the user. It exists same simple way using php? – Luca Zappi Aug 17 '22 at 12:06
  • https://www.educative.io/answers/how-to-read-a-users-input-to-the-php-console would seem far more efficient – Chris Davies Aug 17 '22 at 12:56
  • @roaima Please note that my example offers the user a request with a pre-filled response. This is not considered in the way you have suggested. – Luca Zappi Aug 17 '22 at 14:14
  • 1
    PHP's readline library, as suggested by @roaima, does indeed allow for pre-filled response strings. I have added a reply which attempts to solve your original problem without creating the artifact problem of having to suss out quoting rules when making unnecessary calls to exec and bash. – Jim L. Aug 17 '22 at 22:23

3 Answers3

4

You need to do some quoting for both the code passed to bash and the code passed to sh (as started by php's exec()):

$preFill = 'I m reach';
$prompt = 'Check to confirm string: ';
$bash_code = 'IFS= read -re -p ' .
              escapeshellarg($prompt) .
              ' -i ' .
              escapeshellarg($preFill) .
              ' input; printf "%s\n" "$input"';
$sh_code = 'exec /bin/bash -c ' . escapeshellarg($bash_code);
$output = exec($sh_code);
echo $output;

You also forgot the IFS= and -r for read, and some quotes around shell parameter expansions, and remember echo can't be used for arbitrary data.

Launching both sh and bash to prompt the user seems a bit overkill.

2
exec('/bin/bash -c \'read -e -p "... " -i "'.$preFill.'" input; echo $input\'');

There's a few things wrong here. First, you're running a shell (via exec) to run bash -c ..., which means that the code that Bash eventually runs must be quoted for the intermediate shell too. You could avoid that by using a function that allows you run bash directly, with the arguments in separate array elements (or such), without involving an intermediate shell. Possibly something like proc_open().

Second, you're embedding data (that $preFill variable) within the code you're passing to Bash. With proper quoting and nice values of the variable, that can work, but it turns painful if $preFill can e.g. contain quotes itself. And that pain turns into security vulnerabilities if the variable can be controlled by a user. A better way would be to pass the desired value as a command line argument to the shell, or as an environment variable, and then refer to $1 or $val in the shell program to get the value.

And well, running a shell (or two) for read seems exceedingly silly. It might be better to spend some time finding a better way to do that directly from PHP.

ilkkachu
  • 138,973
2

Given your Problem Y, how does one properly escape a string when using PHP to invoke a system shell to take advantage of bash's read/readline functionality, you have received some excellent answers on how to do so.

This post attempts to address your Problem X -- your original problem -- which I believe is, how can one capture user input in PHP while also supplying a pre-filled default value? By employing a native PHP solution, the entire issue of gnarly multi-level character escaping can be avoided, as @roaima hinted at in the comments of your post.

The PHP documentation for readline includes an example by user taneli circa 2009 that shows how to do what you are seeking without having to resort to external shell invocations. I have expanded that example slightly, but I repeat taneli's work here:

$ cat test.php
<?php

function readline_callback($ret) { global $prompt_answer, $prompt_finished; $prompt_answer = $ret; $prompt_finished = TRUE; readline_callback_handler_remove(); }

$prompt_string = 'Check to confirm string: ';

readline_callback_handler_install($prompt_string, 'readline_callback');

$preFill = 'foobar'; for ($i = 0; $i < strlen($preFill); $i++) { readline_info('pending_input', substr($preFill, $i, 1)); readline_callback_read_char(); }

$prompt_finished = FALSE; $prompt_answer = FALSE;

while (!$prompt_finished) readline_callback_read_char();

echo 'You wrote: ' . $prompt_answer . "\n";

$prompt_string = 'Check to confirm another string: '; readline_callback_handler_install($prompt_string, 'readline_callback');

$preFill = $prompt_answer; for ($i = 0; $i < strlen($preFill); $i++) { readline_info('pending_input', substr($preFill, $i, 1)); readline_callback_read_char(); }

$prompt_finished = FALSE; $prompt_answer = FALSE;

while (!$prompt_finished) readline_callback_read_char();

echo 'You wrote: ' . $prompt_answer . "\n";

?>

Output:

$ php test.php 
Check to confirm string: foobar

I'll append " is the default" and press Enter.

You wrote: foobar is the default
Check to confirm another string: foobar is the default

I'll press Home and put Now " at the beginning of the string. Then I'll press End and put " is it's own default. at the end of the string, and press Enter

You wrote: Now "foobar is the default" is it's own default.
Jim L.
  • 7,997
  • 1
  • 13
  • 27