Serverside Webscripting [JLW322]

05.files&folders

Before we jump in...

Permissions

  • In order to manipulate files/folders, the webserver needs permission to do so
    • Linux and the like: chmod
    • Windows: NTFS permissions (disable simple file sharing!)

open_basedir

  • PHP can restrict which directories you can access
    • E.g. Websites residing in C:\wamp\www have no business in C:\
  • In php.ini the open_basedir setting limits this
  • If you try to read in a non-allowed path, PHP will tell you:
    warning: file_get_contents() [function.file-get-contents]: open_basedir restriction in effect. File(/tmp/../a) is not within the allowed path(s): (/home/:/usr/lib/php:/usr/local/lib/php:/tmp) in /home/public_html/test.php on line 14.

post_max_size and upload_max_filesize

  • PHP can restrict the filesize of a POST request and uploads
  • In php.ini the post_max_size and upload_max_filesize settings limit this
    • post_max_size limits the size of a POST request, including files
    • upload_max_filesize limits the size of an uploaded file

Predefined Constants

  • DIRECTORY_SEPARATOR is a predefined constant containing the directory separator
    • \ on Windows, / on *nix
    • Use of it is not mandatory as / works fine on Windows too

Magic Constants

  • Getting to know the current directory of the PHP file
    • Oldskool: ./ = current working directory (*)
      $myBaseDir = './';
    • Since PHP 4.2: __FILE__ = The full path and filename of the PHP file.
      $myBaseDir = dirname(__FILE__);
    • Since PHP 5.3: __DIR__ = The full path of the PHP file.
      $myBaseDir = __DIR__;
  • → Use __DIR__
(*) For now consider this to be the relative path of the PHP file (but that's not always true)

Kicking it oldskool

Reading Files (1)

  • Available PHP functions
    • fopen() — Opens file or URL, returns a file pointer on success
    • fread() — Binary-safe file read
    • fwrite() — Binary-safe file write
    • fgets() — Gets line from file pointer
    • fseek() — Seeks on a file pointer
    • feof() — Tests for end-of-file on a file pointer
    • rewind() — Rewind the position of a file pointer
    • fclose() — Closes an open file pointer
  • Combination of these needed to actually reading in a file

Reading Files (2)

  • Example
    <?php
    
    	// path to file (relative from this PHP file)
    	$filename = __DIR__ . '/testfile.txt';
    
    	// open the file in read mode
    	$handle = fopen($filename, 'r');
    
    	// file was opened, read in the contents
    	if ($handle) {
    		$contents = fread($handle, filesize($filename));
    	} else { // file not opened
    		$contents = 'Error while opening file!';
    	}
    
    	// close the filehandle
    	fclose($handle);
    
    	// output the fetched contents
    	echo $contents;
    
    // EOF

Reading Files (3)

  • Example 2
    <?php
    
    	$handle = fopen(__DIR__ . '/testfile.txt', 'r');
    
    	if ($handle) {
    		while (!feof($handle)) {
    			$buffer = fgets($handle, 20);
    			echo $buffer . '<br />' . PHP_EOL;
    		}
    		fclose($handle);
    	}
    
    // EOF

More filereading

  • Get all lines of a file in an array using file()
    <?php
    
    	$lines = file(__DIR__ . '/testfile.txt');
    
    	// Loop through our array, show line and line numbers too.
    	foreach ($lines as $line_num => $line) {
    		echo '<strong>#' . $line_num . ':</strong> ' . $line . '<br />';
    	}
  • Get the entire file contens using 1 command
    <?php
    	echo file_get_contents(__DIR__ . '/testfile.txt');

Writing files

  • Again a combination of fopen() and a few other functions
  • Or just use file_put_contents()
    <?php
    
    	file_put_contents(__DIR__ . '/testfile2.txt', 'hello!' . PHP_EOL);
    • To update a file use file_put_contents() with a flag
      <?php
      
      	file_put_contents(
      		__DIR__ . '/testfile2.txt',
      		'hello!' . PHP_EOL,
      		FILE_APPEND
      	);

File properties

  • Example
    <?php
    
    	$filename = __DIR__ . '/testfile.txt';
    
    	echo '<p>The file ' . $filename . (file_exists($filename) ? 'exists' : 'does not exist') . '</p>' . PHP_EOL;
    
    	echo '<p>The file ' . $filename . ' was last modified on ' . date('Y-m-d H:i:s', filemtime($filename)) . '</p>' . PHP_EOL;
    
    	echo '<p>The file ' . $filename . ' is ' . (is_dir($filename) ? '' : 'not') . ' a directory</p>' . PHP_EOL;
    
    	echo '<p>The file ' . $filename . ' is ' . (is_file($filename) ? '' : 'not') . ' a file</p>' . PHP_EOL;
    
    	echo '<p>The file ' . $filename . ' is ' . (is_link($filename) ? '' : 'not') . ' a shortcut</p>' . PHP_EOL;

File manipulation

  • Example
    <?php
    
    	// Copy file
    	copy(__DIR__ . '/testfile.txt', __DIR__ . '/copiedfile.txt');
    
    	// Rename file
    	// @note if the path differs you can move a file with this function!
    	rename(__DIR__ . '/copiedfile.txt', __DIR__ . '/testfile2.txt');
    
    	// Delete file
    	unlink(__DIR__ . '/testfile2.txt');

Listing directories

  • Example
    <?php
    
    	$myBaseDir	= __DIR__;
    	$dp = opendir($myBaseDir);
    
    	// read base directory
    	while (($file = readdir($dp)) !== false) {
    
    		// exclude . and ..
    		if ($file == '.') continue;
    		if ($file == '..') continue;
    
    		// we don't want directories
    		if (is_dir($myBaseDir . '/' . $file)) continue;
    
    		// output the filename
    		echo $file . '<br />' . PHP_EOL;
    
    	}
    
    	// close base directory pointer
    	closedir($dp);

Dealing with errors

  • Suppress errors with @ and stop the process when neccessary
    <?php
    
    	$myBaseDir	= __DIR__;
    	$dp = @opendir($myBaseDir) or die('Error reading ' . $myBaseDir);
    
    	...
Best practice: always place an @ before functions that deal with files/folders

Out with the old, in with the new

SPL

  • SPL = Standard PHP Library
  • A collection of interfaces and classes that are meant to solve standard problems
  • Designed to traverse aggregate structures (anything you want to loop over)
    • ... such as the lines of a file or directories
  • Inluded in PHP since PHP 5.0
  • Enabled by default since 5.3

SPL and files (1)

  • SPL contains a SPLFileInfo class, an object oriented interface to information for an individual file (phpdocs)
  • SPL contains a SplFileObject class — which extends SPLFileInfo — an object oriented interface for an individual file (phpdocs)

→ Instead of using the oldskool functions, create an instance of this class and access the datamembers/ functions on it

SPL and files (2)

  • Example (file info)
    <?php
    
    	$filename = __DIR__ . '/testfile.txt';
    	$fi = new SplFileInfo($filename);
    
    	echo '<p>The file ' . $fi->getFileName() . ' was last modified on ' . date('Y-m-d H:i:s', $fi->getMTime()) . '</p>' . PHP_EOL;
    
    	echo '<p>The file ' . $fi->getFileName() . ' is ' . ($fi->isDir() ? '' : 'not') . ' a directory</p>' . PHP_EOL;
    
    	echo '<p>The file ' . $fi->getFileName() . ' is ' . ($fi->isFile() ? '' : 'not') . ' a file</p>' . PHP_EOL;
    
    	echo '<p>The file ' . $fi->getFileName() . ' is ' . ($fi->isLink() ? '' : 'not') . ' a shortcut</p>' . PHP_EOL;
    
    // EOF

SPL and files (3)

  • Example (file read)
    <?php
    
    	$lines = new SPLFileObject(__DIR__ . '/testfile.txt');
    
    	// Loop through our array, show line and line numbers too.
    	foreach ($lines as $line_num => $line) {
    		echo '<strong>#' . $line_num . ':</strong> ' . $line . '<br />' . PHP_EOL;
    	}
    
    // EOF
  • Downside of SplFileObject: it's line-oriented
    • → There's no SplFileObject-version of file_get_contents()

SPL and directories

  • SPL contains a DirectoryIterator class — which extends SPLFileInfo (!) — a simple interface for viewing the contents of filesystem directories (phpdocs)
  • Example
    <?php
    
    	$myBaseDir = __DIR__;
    	$di = new DirectoryIterator($myBaseDir);
    
    	foreach ($di as $file) {
    		// exclude . and .. + we don't want directories
    		if (!$file->isDot() && !$file->isDir()) {
    			echo $file . '<br />' . PHP_EOL;
    		}
    	}
    
    // EOF

Dealing with errors

  • If you try to access a file/folder that doesn't exist using the SPL classes, PHP will throw an Exception
    <?php
    
    	$di = new DirectoryIterator('/this/path/does/not/exist');
    
    // EOF
  • Use a try/catch to properly handle it
    <?php
    
    	try {
    		$di = new DirectoryIterator('/this/path/does/not/exist');
    	} catch (Exception $e) {
    		echo 'There was an error: <br />' . $e->getMessage();
    	}
    
    // EOF

Oldskool vs. SPL

Summary / What to use when?

  • Use the SPL classes for
    • Looping directories → DirectoryIterator
    • Fetching file info → SplFileInfo and SplFileObject
    • Reading file contents line by line, Reading CSV files → SplFileObject
  • Use the oldskool functions for
    • Deleting files → unlink()
    • Copying files → copy()
    • Renaming/Moving files → rename()
    • Fetching/Manipulating file contents (in its entirety) → file_get_contents() and file_put_contents()

File Uploads

Handling file uploads (1)

  • Files are uploaded to a temp location. You'll need to move them manually using move_uploaded_file()
  • PHP will populate a superglobal named $_FILES containing that temp location per file.
Don't forget: make sure your webserver has the proper permissions!

Handling file uploads (2)

  • Example
    <?php
    if (isset($_FILES['avatar'])) {
    
    	echo '<p>Uploaded file: ' . $_FILES['avatar']['name'] . '</p>';
    	echo '<p>Temp location: ' . $_FILES['avatar']['tmp_name'] . '</p>';
    	echo '<p>Size: ' . $_FILES['avatar']['size'] . '</p>';
    
    	if (!in_array((new SplFileInfo($_FILES['avatar']['name']))->getExtension(), array('jpeg', 'jpg', 'png', 'gif'))) {
    		exit('<p>Invalid extension. Only .jpeg, .jpg, .png or .gif allowed</p>');
    	}
    
    	@move_uploaded_file($_FILES['avatar']['tmp_name'], __DIR__ . DIRECTORY_SEPARATOR . $_FILES['avatar']['name']) or die('<p>Error while saving file in the uploads folder</p>');
    
    	echo '<p><img src=' . $_FILES['avatar']['name'] . ' alt="" /><p>';
    }
    
    ?>
    <form action="<?php echo $_SERVER['PHP_SELF'];?>" method="post" enctype="multipart/form-data" >
    	<input type="file" name="avatar" id="avatar" /><input type="submit" />
    </form>

Security / Best Practices

  • Provide a separate, dedicated folder to store all uploads in
    • e.g. files/
    • Use subfolders where appropriate, e.g. files/photoalbums/1 and files/photoalbums/2
  • Rename files when moving them to prevent name conflicts
    • e.g. give photos in photoalbums a sequential name such as 1.jpg, 2.jpg, etc.
  • Always limit the allowed file extensions
    • You don't want one to upload a .php file, right?

Questions?

Code summary

A code-only summary of this chapter is available at 05.files.and.folders.summary.html

Note: not all information from these slides can be found in this summary!

Sources

ikdoeict.be