News:

SMF 2.1.4 has been released! Take it for a spin! Read more.

Main Menu

SSI.php issue

Started by ArchVile, July 31, 2015, 11:19:37 PM

Previous topic - Next topic

ArchVile

I'm not sure this is the correct board for this question. I have an SMF 2.0.10 forum and am trying to integrate access to large files which can't be attached to posts because they're over the php limit. It seems the best way around the issue is to write a php script which gets the info it needs from SMF to determine access, and then serve the file itself with readfile().

I read up on SSI.php which seems the best way to go. I followed the instructions and had instant access to $user_info['groups'], which is exactly what I needed. However, I soon ran into the first problem: I am on shared hosting and only have 64MB of ram to work with for php. As soon as php goes to serve the file with readfile(), I get:

PHP Fatal error:  Allowed memory size of 67108864 bytes exhausted (tried to allocate 27987165 bytes) in /home/username/public_html/forum/Sources/QueryString.php on line 483

That doesn't even make sense that it would happen in SMF's code given it occurs at the call to readfile() (which I verified by taking out readfile, no more memory exceeded error). With get_defined_vars(), I saw that SSI is loading a TON of stuff into variables I don't need. I only need $user_info['groups'], so I manually unset everything SSI loads into memory. This fixed the memory exceeded error, and people can now download the file successfully, but I am now getting this message in my web server php error log every time someone accesses the php script to successfully download the file:

PHP Fatal error:  Function name must be a string in /home/username/public_html/forum/Sources/Load.php on line 2722

So, to sum up, two main issues

1) SMF's SSI seems to load a ton of stuff into many different variables which takes up enough memory to cause readfile() to fail with memory exceeded message. Is there any way to tell SSI.php to free the data my php code doesn't need?

2) Using require("/home/username/public_html/forum/SSI.php"), it seems that the server is executing the php files in parallel, as I am getting error messages in completely unrelated php files (load.php, querystring.php) at certain lines in my own php file. I even tried forcing it to halt execution after it was done with die(), but I'm still getting the function name must be string message in the server php error log.

In short, it seems that after require(), there's no guarantee that the php file in question (and any recursively referenced ones IT refers to itself) have finished being executed by the time the lines of php code after the require call are evaluated. Is there any way to guarantee the php files referenced in the require line have COMPLETELY FINISHED executing after said require line?

Here's the code:


$ssi_guest_access = false;

require("/home/username/public_html/forum/SSI.php");

// Get the person's forum username and forum usergroups from SMF's SSI.php
$groups = $user_info['groups'];
$user = $user_info['username'];

// Memory freeing - Need to free up all the variables that SSI loaded in EXCEPT
// for what's needed - $groups and $user

$keepVars = array('groups', 'user');
$vars = array_keys(get_defined_vars());
$vars = array_diff($vars, $keepVars);


// Free from memory.
for ($i = 0; $i < sizeOf($vars); $i++)
{
    unset($$vars[$i]);
}
unset($vars,$i);


$folder_path = "/home/username/protected_files/";
$file_name = "file.7z";
$file_path = $folder_path . $file_name;

// The forum's usergroups which are allowed to download this file
$authedGroups = array(1, 3, 6, 14, 18);

// Take the set intersection of the two arrays..
$groups = array_intersect($authedGroups, $groups);

// ..and then ensure their values are indexed from 0 to n. Their group id is needed in unrelated code which has been omitted for clarity.
$groups = array_values($groups);

// Empty set means unauthorized, so halt
if (count($groups) < 1)
    die('You are not authorized to access this area.');

// Check if file exists, and serve it with readfile if so.
if (file_exists($file_path))
{
    header("Content-type: application/octet-stream");
    header("Content-Disposition: attachment; filename=$file_name");
    header("Content-Length: " . filesize($file_path));
    readfile($file_path); // <-- Memory limit exceeded error happens in
  // forum/Sources/QueryString.php line 483 when this
  // line left in and the memory freeing code above is
  // commented out.
    die('');
}

else
    die('Fatal error reading file.');


Suki

The allowed memory issue references SMF because SMF by default "controls" every bit thats happening on a request, in this case, the entire file is been added to SMF's buffer.

To solve this you need to kill anything SMF related and start a new output buffer:

// Kill anything else
ob_end_clean();

if (!empty($modSettings['CompressedOutput']))
@ob_start('ob_gzhandler');

else
ob_start();

my headers and readfile() call here



This will prevent SMF from putting your file into its buffer and instead just serve the file directly to the browser.
Disclaimer: unless otherwise stated, all my posts are personal and does not represent any views or opinions held by Simple Machines.

ArchVile

I removed the memory freeing unset code, and instead added in the code you provided. It seems to have fixed the problem, the file downloads successfully, and no more messages in the php log. Thanks for explanation and help 8)

Advertisement: