Category: "PHP"

PHP File Upload Example

This example shows the HTML and PHP to upload a file.

Browsers handle the MAX_FILE_SIZE input and accept attribute differently. Some will filter the files offered through the dialog box to only list those identified by the accept attribute. Some will allow you to select a file that is too large, and submit it to the server, without any content. In this case, the tmp_name is empty, error is 2, and size is 0.

In all cases you must validate the file on the server side.

HTML to upload a file:


<?php define('FILE_UPLOAD_MAX',min(str_replace('M','000000',ini_get('upload_max_filesize')),str_replace('M','000000',ini_get('post_max_size'))));
 ?>
<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>upload demo</title>
		<link href="upload.css" rel="stylesheet">
	</head>
	<body>
		<h1>upload demo</h1>
		<form method="post" enctype="multipart/form-data" action="upload-file.php">
			<input type="hidden" name="MAX_FILE_SIZE" value="<?= FILE_UPLOAD_MAX ?> " >
			<div class="block">
				<label for="file">File</label>
				<input type="file" name="file" id="file" value="" accept="image/*">
			</div>
			<button type="submit">Go</button>
		</form>
	</body>
</html>

PHP to accept it:


<?php
header('Content-Type: text/plain');
define('MAX_FILE_SIZE',min(
        str_replace('M','000000',ini_get('upload_max_filesize')),
        str_replace('M','000000',ini_get('post_max_size'))));

function is_accepted($sAccept,$sFiletype) {
        $aAccept=explode(',',$sAccept);
        return in_array($sFiletype,$aAccept,TRUE);
}

function size_ok($iMaxSize,$iSize) {
        return $iSize <= $iMaxSize;
}

function upload(&$files,$sAccept='*/*',$iMaxSize=MAX_FILE_SIZE) {
        foreach ($_FILES as $k => $v) {
                if ($v['tmp_name']==='') {
                        continue;
                }
                // More robust type checking: https://www.php.net/manual/en/features.file-upload.php#114004
                if (!is_accepted($sAccept,$v['type'])) {
                        throw new Exception($v['type'].' files cannot be sent');
                }
                if (!size_ok($iMaxSize,$v['size'])) {
                        throw new Exception('File cannot be larger than '.$iMaxSize);
                }

               // do antivirus scan here
               $vname = $v['tmp_name'];
               system('clamscan '.escapeshellarg($vname),$result);
               echo $result;
               if ($result !== 0) {
                  die;
              }
              echo 'Scanned okay';
                // Thanks to: https://www.php.net/manual/en/features.file-upload.php#114004
                // strip out anything other than letters, digits, underscores, periods and dashes
                $name = preg_replace('/[^\w.-]/','',$v['name']);
                if ($name === '') {
                        throw new Exception('Invalid filename');
                }
                if (!move_uploaded_file($v['tmp_name'], $name)) {
                        throw new Exception('Upload failed');
                }
                $element = [
                        'filename' => $name,
                        'file' => $v['tmp_name'],
                        'content_type' => $v['type'] ];
                $files[] = $element;
        }
        return true;
}

$uploadFiles = [];
try {
        if (upload($uploadFiles, 'image/png,image/jpg,image/jpeg,image/gif', MAX_FILE_SIZE)) {
                var_dump($uploadFiles);
        } else {
                echo 'Upload failed';
        }
} catch (Exception $e) {
        echo $e->getMessage().PHP_EOL;
        error_log('File upload failed '.$e->getMessage());
}

Open Source - Cycle Back

Open Source software is often maturing.

Cycling back to the distribution site has many advantages:

  • Upgrading open source components usually delivers many bug fixes and new features. Although upgrading after ever update is probably unnecessary, it is worth considering an upgrade if something isn’t working exactly as expected.
  • Upgrading increases the lifespan of your code, as well as often improving the quality.
  • Documentation often improves, both directly from the author and with contributions from other users. Repeat visits usually increases understanding.
  • As new features are added, they can be integrated into your code.
  • As you learn more, you can contribute to the site.

Cycling back through your application also has many advantages:

  • As your skills improve, your code will, too. Revisiting code allows you to improve it.
  • Look for ways to consolidate and reuse code. The less code you have to maintain, the better. Overly complex or large applications are often more difficult to work with than sleek, elegant systems.
  • Validate XHTML and CSS periodically.
  • Use jsLint, and other tools.
  • Run the pages through different browsers after major changes.
  • Keep an eye on the architecture, understand what goes where and why.
  • Add comments for things that are difficult to understand.
  • Look for opportunities to improve performance.

PHP Session Development Strategies

  • Use Firebug to get the session id off the page requests.
  • Find the directory session files are stored in. On busy, shared servers, this is often /tmp. Check /etc/php.ini or possibly /etc/httpd/conf.d/php.conf.
  • To simulate a session timeout on the server side, delete the session file.
  • To simulate a cookie timeout on the client side, delete all private data (FF) or cookies (IE).
  • To simulate a lost connection - disconnect or disable the network connection.
  • Use more to view the contents of a session file.
  • Use session files to reduce the amount of logic required for execution. You can assume the session file must be read for authentication, therefore, adding a few more bytes in to reduce execution time should yield a performance gain. Be careful to only store data which is valuable for enough operations that the storage increase is warranted.
  • ACL can be stored in session files. It may be faster than using a database.
  • Never store passwords in session data.
  • Language in use is good for storage in session files.
  • Session files must not be accessible through the web. Should only be accessible for appropriate users.

Monitor Script Execution with PHP

The following is a very simple script that can be used as a framework to monitor the execution of a script.

Comments are used for explanation.

<?php
/* Ensure the viewer has logged in. */
session_start();
if (!isset($_SESSION['bLogged_in']))
	die();
?>
<html>
<head>
<?php
        /* 
        The escapeshellarg is there as a reminder to escape any input.  In this
        case, it is unnecessary, but if $_GET was used, it would be important.
        */
	$script=escapeshellarg('httpd');

        /* 
        Use ps and grep to check for the script running.  ps -C $script may also be used.
        */
	$ps=nl2br(`ps|grep $script`);

        /* 
        This command is used to simulate execution of a script.  It just appends the date
        to a file.  
        */
	$time_data_maker=`date >> ../test.data`;

        /* 
        Test to see if the command is executing
        */
	if ($ps != '')
        {
                /* Update the timestamp on the session file */
                $_SESSION['tick']=1;
                /* Tell the browser to refresh the page in 3 seconds */
		echo '<META HTTP-EQUIV="Refresh" CONTENT="3" />'."\n";
        }
?>
<title><?php $_GET['title']?></title>
<style>
body 
{ 
	font-family : "Courier New", Courier, monospace;
	font-size: .8em; 
}
</style>
</head>
<body>
<?php
/* If script is not running ... */
if ($ps == '')
{
	echo $script.'is not running<br />';
}
else
{
	echo $script.'is running<br />Current Status<br />';
        /* Display contents of file */
	echo nl2br(file_get_contents('../test.data'));
}
?>
</body>
</html>

Notes:

  • Refreshes stop when script stops
  • Page display prolongs session until it finishes
  • Session id may be used to identify data file, script name can make a nice extension
  • Placed in an iframe, with a print button, this is a very nice interface

MySQL REGEXP Validation / Error Handling

Allowing users to run regular expression (REGEXP) searches through a web interface provides excellent search capabilities with very little engineering. One need only change WHERE `field`=’value’ to WHERE `field` REGEXP ‘regex’.

A problem arises if the regex entered is invalid. MySQL will report an error.

One solution that works well is to wrap the mysql_query string in a function or class method. Then, when testing for an error, check (use stristr) to see if the error was a regex error. Regex errors should be handled as user errors, not application errors. They should be logged to allow later analysis (if there are alot of regex errors, help should be provided).

Another approach is to use the PREPARE STATEMENT (see link above). This will throw an error if there are any invalid regexes.