Restrict allowed file upload types PHP - php

Right now I have a function which takes my uploaded file, checks the extension, and if it matches an array of valid extensions it's processed. It's a contact list importer.
What I need to figure out is how to be sure that file (in this case a .csv) is actually what it says it is (ex. not an excel file that just got renamed as a .csv).
Our servers run PHP 5.2.13
Here's the current validation function I have
public static function validateExtension($file_name,$ext_array) {
$extension = strtolower(strrchr($file_name,"."));
$valid_extension="FALSE";
if (!$file_name) {
return false;
} else {
if (!$ext_array) {
return true;
} else {
foreach ($ext_array as $value) {
$first_char = substr($value,0,1);
if ($first_char <> ".") {
$extensions[] = ".".strtolower($value);
}
else {
$extensions[] = strtolower($value);
}
}
foreach ($extensions as $value) {
if ($value == $extension) {
$valid_extension = "TRUE";
}
}
if ($valid_extension==="TRUE") {
return true;
} else {
return false;
}
}
}
}
EDIT: I'm now trying to do
exec('file -ir '.$myFile)
When I run this command in terminal I'm given a usable response. When I run the same command through php, I'm given something different. Any ideas why? I've tried it with exec, passthru, shell_exec. And the server does not have safe mode running.

Forget extension checking, it's not reliable enough.
Also, I think traditional MIME magic sniffing will fail here, because there is no usable header (This is just my guess, though.)
In this specific case, I'd say it's feasible to take a quick peek at the contents, for example read the first ten lines or so. If they are all no longer than x bytes, and each line contains the same number of semicolons (or whatever your CSV parser takes as separators), it's a CSV file.

Related

Why could a PHP function output different results under the same arguments? (PHP Syntax Detection)

I've created a PHP function that checks if a PHP code-block has syntax error or not. This is how the function looks like:
function isValidPHP($str) {
$uploads_dir = trailingslashit(wp_upload_dir()['basedir']) . 'folder';
$filename = tempnam($uploads_dir, '_');
file_put_contents($filename, $str);
exec("php -l {$filename}", $output, $return);
$output = trim(implode(PHP_EOL, $output));
if ($return == 0) {
return true;
} else {
return false;
}
}
In another function I'm using the function above the check if a specific php code block is valid:
if (isValidPHP($main_file) == true) {
return "Valid";
} else {
return "Not Valid PHP";
}
The strange part: For one of our users the isValidPHP function returns false, even if there are syntax errors. We tried it with the same php string. On my server environment the syntax error are detected, on his environment not.
What could be a logical reason for this? Could the PHP version causes the error?
Or do I need to adjust the condition operator from == to the type based ===?
Can you think of a possible reason why there are different results for the same string?
By the way: this functions are implemented on a WordPress installation as plugin.

Check if the text file was empty

I'd like to know how to check if a text file is empty or not. It means that there is no text even some space, i.e. it was blank
function keyRemain($path)
{
$ambil = file_get_contents("data/$path/keywords.txt");
$kw = explode(",", $ambil);
if (count($kw) > 1) {
return false;
} else {
return true;
}
}
You have to check the empty function along with trim
function keyRemain($path)
{
$ambil = trim(file_get_contents("data/$path/keywords.txt"));
var_dump($ambil); // check the output here
if(!empty($ambil)) {
return false;
} else {
return true;
}
}
Maybe this was not the answer, just the another way to check the file. Before this was happend, the code appear instead the class. After i cut it and move it outside of the class it work perfectly without any errors.
file_get_contents() will read the whole file while filesize() uses stat() to detirmine the file size. Use filesize(), it should consume less disk I/O.
That's the answer found here, on stack...
You can also (on same link there's this answer):
clearstatcache();
if(filesize($path_to_your_file)) {
// your file is not empty
}

What's the difference between fgets and current?

In the PHP manual, the SplFileObject has two methods that seem very similar:
$file->fgets()
Gets a line from the file.
$file->current()
Retrieves the current line of the file.
The documentation on procedural fgets is even closer to current():
Gets line from file pointer.
But there's no note about one being an alias of the other. Both take no parameters. What is the difference between these?
The real difference that matter is, e.g. we have this file
foo
bar
The below function will print foo bar
$file = new \SplFileObject("test.txt");
while (!$file->eof()) {
echo $file->fgets();
}
But this function will print foo continuously
$file = new \SplFileObject("test.txt");
while (!$file->eof()) {
echo $file->current();
}
Because fgets starts from begin and reading the next line which is the first line, then it reads the next line which is the seconds line and stops because it found end of file but current always read the current line and never goes to next line so it never breaks of the loop, you need to use next function to read next line, so the first code is equivalent with:
$file = new \SplFileObject("test.txt");
while (!$file->eof()) {
echo $file->current();
$file->next();
}
Edit: also check Josiah answer about the difference with flag and Axalix to see the source code diffrence
One difference is that current can function as fgetcsv or fgets depending on whether the SplFileObject::READ_CSV flag is set. The underlying implementation is almost the same (without moving the pointer, see other answers) as either, depending on that flag.
This means that current can either return a string or an array, depending on the presence of the flag.
Presumably this is done for code portability, though it seems like it would be more code than fgetcsv to accomplish the same work, and perhaps minutely less performant due to the extra logical call (see Axalix's answer).
There are some differences in implementation. It seems like fgets is shorter and it only does what is says Reads the line from a file
https://github.com/php/php-src/blob/4d9a1883aa764e502990488d2e8b9c978be6fbd2/ext/spl/spl_directory.c
/* {{{ proto string SplFileObject::current()
Return current line from file */
SPL_METHOD(SplFileObject, current)
{
spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis());
if (zend_parse_parameters_none() == FAILURE) {
return;
}
if(!intern->u.file.stream) {
zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized");
return;
}
if (!intern->u.file.current_line && Z_ISUNDEF(intern->u.file.current_zval)) {
spl_filesystem_file_read_line(getThis(), intern, 1);
}
if (intern->u.file.current_line && (!SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_CSV) || Z_ISUNDEF(intern->u.file.current_zval))) {
RETURN_STRINGL(intern->u.file.current_line, intern->u.file.current_line_len);
} else if (!Z_ISUNDEF(intern->u.file.current_zval)) {
RETURN_ZVAL(&intern->u.file.current_zval, 1, 0);
}
RETURN_FALSE;
} /* }}} */
VS
/* {{{ proto string SplFileObject::fgets()
Rturn next line from file */
SPL_METHOD(SplFileObject, fgets)
{
spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis());
if (zend_parse_parameters_none() == FAILURE) {
return;
}
if(!intern->u.file.stream) {
zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized");
return;
}
if (spl_filesystem_file_read(intern, 0) == FAILURE) {
RETURN_FALSE;
}
RETURN_STRINGL(intern->u.file.current_line, intern->u.file.current_line_len);
} /* }}} */
Edit:
So the difference is in return. current (if some flags are set) may return RETURN_ZVAL (php array for this case) OR string OR. fgets returns strings or FALSE. Also if (spl_filesystem_file_read(intern, 0) == FAILURE) { which is way faster than anything else if we just want to read a line from a file w/o making any other work.

PHP APC problems when storing objects

I am trying to build an configuration parser for my application I installed APC today, but everytime I try to put an serialized object in the store, it does not get in there and does not. (I am checking with apc.php for my version[3.1.8-dev] on PHP 5.3.16 [My Dev Environment], so I am sure that the data is not in the cache). this is how I pass the data to the cacher:
// The data before the caching
array (
'key' => md5($this->filename),
'value' => serialize($this->cfg)
);
// The caching interface
function($argc){
$key = $argc['key'];
Cache\APC::getInstance()->set($key,$argc['value']);
}
// The caching method described above
public function set($key, $val) {
if (apc_exists($key)) {
apc_delete ($key);
return apc_store($key, $val);
}
else
return false;
}
// the constructor of the configuration class.
// It 1st looks for the configuration in
// the cache if it is not present performs the reading from the file.
public function __construct($filename = '/application/config/application.ini',
$type = self::CONFIG_INI)
{
if (defined('SYSTEM_CACHE') && SYSTEM_CACHE === 'APC'){
$key = md5($filename);
$cfg = APC::getInstance()->get($key);
if (!empty($cfg)) {
print "From Cache";
$this->cfg = unserialize($cfg);
return;
} else {
print "From File";
}
}
}
I did a few tests and there is not a problem with the MD5() key (which I thought while writing this question) nor with APC itself. I am really stuck on this one, nothing odd in the logs, so if anyone can give me at least some directions will be very appreciated.
Thanks in advance!
The problem is was in my code:\
public function set($key, $val) {
/*
*
* If the key exists in the cache delete it and store it again,
* but how could it exist when the else clause is like that...
*/
if (apc_exists($key)) {
apc_delete ($key);
return apc_store($key, $val);
}
// This is very wrong in the current case
// cuz the function will never store and the if will
// never match..
else
return false;
}
NOTE:
Always think and keep your eyes open, if you still can't find anything get off the PC and give yourself a rest. Get back after 10-15 minutes and pown the code. It helps! :D

Adding files to a Tar Archive in PHP with different filenames

I'm currently using the Archive-Tar Pear extension for PHP to add a collection of files into a Tar Archive.
These files are stored on a filer with an extra extension
e.g.
filename.tgz.104850209.t or filename2.doc.2154395.t
I'd like to remove this extra extension while adding the files so that my Tar Archive would have the files: filename.tgz and filename2.doc
Is there a way of doing that without having to copy/rename the source files first before adding to the Archive?
Thanks,
Mark.
Archive_Tar in its latest version does not yet support such a functionality out of the box. Part of the functionality is in _addFile() and the other part in _addString().
Most easy is probably to extend from Archive_Tar and proxy all calls to _writeHeaderBlock() which is public, applying a map on the filename parameter so to rename it when written into headers.
class Patched_Archive_Tar extends Archive_Tar
{
var $renameMap = array();
function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0,
$p_type='', $p_uid=0, $p_gid=0)
{
return parent::_writeHeaderBlock($this->_translateFilename($p_filename),
$p_size, $p_mtime=0, $p_perms=0,
$p_type='', $p_uid=0, $p_gid=0);
}
function _translateFilename($orignal)
{
$map = $this->renameMap;
if (isset($map[$orignal])) {
return $map[$orignal];
}
return $original;
}
}
Usage:
$obj = new Patched_Archive_Tar('dummy.tar'); // name of archive
$files = array('mystuff/ad.gif',
'mystuff/alcon.doc.t',
'mystuff/alcon.xls.t'); // files to store in archive
$obj->renameMap = array(
'mystuff/alcon.doc.t' => 'mystuff/alcon.doc',
'mystuff/alcon.xls.t' => 'mystuff/alcon.xls',
) // files to rename
if ($obj->create($files)) {
echo 'Created successfully!';
} else {
echo 'Error in file creation';
}
This is quick and dirty but hopefully worky. For something better see the function I noticed at the beginning _addFile() and _addString(), you basically want another one that is able to add a file (as with _addFile()) by specifiying the filename (as with _addString()).
Tried to edit #hakre's answer, but peer reviewers weren't having that.
To answer #user2248522's comment, I rewrote the class to use _writeHeader. Additionally, I added a block for any Windows users out there and fixed a couple spelling errors.
class Patched_Archive_Tar extends Archive_Tar
{
var $renameMap = array();
function _writeHeader($p_filename, $p_stored_filename)
{
return parent::_writeHeader($p_filename,
$this->_translateFilename($p_stored_filename));
}
function _translateFilename($orignal)
{
$map = $this->renameMap;
if (isset($map[$original])) {
return $map[$original];
}
//Need alter our map array to match the altered original on WIN systems
if (defined('OS_WINDOWS') && OS_WINDOWS) {
//Check for a proper array
if (!is_array($map)) return $original;
//Check each replacement rule
foreach($map as $needle => $replacement) {
if ($this->_translateWinPath($needle, true) == $original) {
return $replacement;
} //if()
} //foreach()
} //if()
return $original;
}
}
Usage:
$obj = new Patched_Archive_Tar('dummy.tar'); // name of archive
$files = array('mystuff/ad.gif',
'mystuff/alcon.doc.t',
'mystuff/alcon.xls.t'); // files to store in archive
$obj->renameMap = array(
'mystuff/alcon.doc.t' => 'mystuff/alcon.doc',
'mystuff/alcon.xls.t' => 'mystuff/alcon.xls',
) // files to rename
if ($obj->create($files)) {
echo 'Created successfully!';
} else {
echo 'Error in file creation';
}

Categories