1

I was running over a sample script and hit on this particular issue.

The script starts off by setting

 $docroot=$_SERVER['DOCUMENT_ROOT'];

For writing to a file,

@$fp = fopen("$docroot/../parts/partsorders.txt",'ab');

is used. But no matter what, this fails to write to the file.

After a tinkering with it for a while, I set the command to

@$fp = fopen('$docroot/../parts/partsorders.txt','ab');

used single quotes instead of double quotes and it worked fine!

My question is, isn't the former double quoted format supposed to work instead of the single quotes. What is happening here ?

Here is the stripped down code, guys (Assume that the file exists in the server) :

    <?php
    $docroot=$_SERVER['DOCUMENT_ROOT'];
    $outputstring = "herpderp";
    ?>
    <html>
    <head>
    <title>Quotes</title>
    </head>
    <body>
    <?php
    @$fp=fopen("$docroot/../parts/partsorders.txt","ab");
    flock($fp, LOCK_EX);
    if(!$fp) {
        echo "<p><strong>Failed.</strong></p></body></html>";
        exit;
    }
    fwrite($fp,$outputstring,strlen($outputstring));
    flock($fp,LOCK_UN);
    fclose($fp);
    echo "<p>Order written.</p>";
    ?>
    </body>
    </html>

4 Answers4

2

There are differences between single and double quoted strings in PHP. Single quoted strings do not cause the expansion of variable names while double quoted strings do. See here. Why your code works when you use the variable name with single quoted strings doesn't make sense to me. Furthermore, it's a bad idea to use @ in front of your fopen commands, it will prevent you from seeing the error output.

GWW
  • 43,129
  • 11
  • 115
  • 108
2

The double quote one is the one that should evaluate $docroot for you. What the single quote should do is try to open a file that actually has $docroot as a string in it's path. Does

@$fp = fopen($docroot . '/../parts/partsorders.txt','ab');

yield the same result?

And do you use @to supress errors? In that case it should be before the function I think

$fp = @fopen($docroot . '/../parts/partsorders.txt','ab');

But don't do that when trying to find errors in your application. The thing is that you could very well get an error that it can't find the file and you just don't notice.

inquam
  • 12,664
  • 15
  • 61
  • 101
  • 1
    `@$fp = fopen($docroot . '/../parts/partsorders.txt','ab');` doesn't work while `@$fp = fopen('$docroot' . '/../parts/partsorders.txt','ab');` does work – Samuel De With May 25 '11 at 16:34
  • 2
    Strange... anyway... Do NOT use `@` to suppress errors. Preferably never but defiantly not when hunting bugs ;) – inquam May 25 '11 at 16:37
0

UPDATE Thanks to pinkgothic for his comment, firstly for providing the correct terminology and secondly for pointing out that this answer is wrong.
I have experimented with the theory that shell is trying to expand the environment variable $docroot and found that shell expansion is not possible in fopen().
I tried the following code and got this error (I have a file called test.txt in my home directory)

$fp = fopen( '$HOME/test.txt', 'rb' );
//PHP Warning:  fopen($HOME/test.txt): failed to open stream: No such file or directory in php shell code on line 1

So unless the OP has some configuration which allows shell expansion in fopen(), my answer is, as I say, incorrect.


The string $docroot/../parts/partsorders.txt is being sent directly to the operating system shell. As there is no $docroot variable set it is replaced by an empty string, so it is the same as using

@$fp = fopen('/../parts/partsorders.txt','ab');

Which is an absolute path starting from /, the document root.

#run from shell (bash)
~$ echo $docroot/../parts/partsorders.txt
/../parts/partsorders.txt
meouw
  • 41,754
  • 10
  • 52
  • 69
  • $docroot was set in the beginning as $docroot=$_SERVER['DOCUMENT_ROOT']; – Samuel De With May 25 '11 at 16:36
  • @Samuel De With: meouw's idea is that `fopen()` tries to open `'$docroot/../parts/partsorders.txt'` with shell expansion, the shell however does not have an environment variable called `$docroot` and thus 'expands' the filename to `'/../parts/partsorders.txt'`. (I don't think this is correct, it'd be a worrysome naive behaviour of PHP's not to shell-quote the filename if it truly does pass it to the a shell.) – pinkgothic May 25 '11 at 16:44
0

After my comment, I had a night's sleep and realised fopen() may not actually use/be like realpath() (which expects all segments to be set, even the ones that are irrelevant for the final normalised path).

It doesn't.

Accordingly, the reason your file is opened is actually fairly simple:

'$docroot/../parts/partsorders.txt'

...which is read as "pastorders.txt in the 'parts' folder which is a folder found in the folder above the '$docroot' folder which is in the current working directory" collapses to...

'parts/partsorders.txt'

...because fopen() simply vanishes $docroot/.. away without checking that $docroot exists. (Functions like realpath() do check it, which was throwing me off.)

So, your file is actually in <current working directory>/parts/partsorders.txt. (Since you're opening the file with the a flag, if it didn't exist there before, it was definitely created there.)

Whatever your $_SERVER['DOCUMENT_ROOT'] contains, it seems it's not what you want. Additionally, in some setups, you can't reasonably .. above $_SERVER['DOCUMENT_ROOT'] - permissions actually won't let you.

If that environment variable outright isn't set (if that's even possible; but I think this demonstrates the problem even if it isn't), the path is quite different:

"$docroot/../parts/partsorders.txt"

...becomes:

"/../parts/partsorders.txt"

...which tries to get up the hierarchy past the root point (/), which would of course not work.

I suggest echoing out or logging $_SERVER['DOCUMENT_ROOT'] and taking a look at what it actually contains, and if it's what you expect it to be.

What might be worth looking for is __DIR__ (or in older PHP versions, dirname(__FILE__)), which takes the directory the file. As long as the file knows where it is, you can just read out files relative to its location.

pinkgothic
  • 6,081
  • 3
  • 47
  • 72