SakhaliaNetHome PageHistory of the RailwayVorKutaAcceptance of cookiesAcceptance of cookies

PHP Tutorial :: Files (III)

PHP Example #107

Checking errors in file_put_contents()

One of the previous examples containing all the error checking code for file_get_contents() and file_put_contents(). When fwrite() and file_put_contents() have success, they return the number of bytes written. When they aren't, fwrite() returns false but file_put_contents() can return false or -1 depending on what went wrong; therefore, we would have to check both possibilities.

<?php
// Checking errors in file_put_contents()
$zip = 10040;
$weather_page = file_get_contents('http://www.srh.noaa.gov/zipcity.php?inputstring=' . $zip);
if ($weather_page === false) {
print "Couldn't get weather for $zip";
} else {
// Finds the first occurrence of "Detailed Forecast" in the webpage
$page = strstr($weather_page, 'Detailed Forecast');
// Finds where it starts the table of the weather forecast
$table_start = strpos($page, '<table>');
// Finds where it ends the table
// It adds 8 characters to pass the closing tag of the table
$table_end = strpos($page, '</table>') + 8;
// Prints the section of $page that contains the table
$forecast = substr($page, $table_start, $table_end - $table_start);
print $forecast;
$saved_file = file_put_contents("weather-$zip.txt", $matches[1]);
// It is necessary to check if file_put_contents() returns false or -1
if (($saved_file === false) || ($saved_file == -1)) {
print "Couldn't save weather to weather-$zip.txt";
}
}
?>
Couldn't get weather for 10040

PHP Example #108

Checking errors in fgets()

One of the previous examples containing all the error checking code for fopen(), fgets() and fclose(). Note how fgets() is compared differently, by using === instead of !, as stated in the previous example.

<?php
// Checking errors in fopen(), fgets() and fclose()
$fh = fopen('people.txt', 'rb');
if (! $fh) {
print "Error opening people.txt: $php_errormsg";
} else {
for ($line = fgets($fh); ! feof($fh); $line = fgets($fh)) {
if ($line === false) {
print "Error reading line: $php_errormsg";
} else {
$line = trim($line);
$info = explode('|', $line);
print '<li><a href="mailto:' . $info[0] . '">' . $info[1] . "</a></li>\n";
}
}
if (! fclose($fh)) {
print "Error closing people.txt: $php_errormsg";
}
}
?>

PHP Example #109

Sanitizing filenames with str_replace()

Samely as the data sent in a form or in an URL can cause problems when they are printed on screen (cross-site scripting) or are placed in a SQL query (SQL injection), it can cause trouble as well when passed as a filename or part of a filename. There are certain characters that have to be escaped to avoid this: slashes (/) and sequences of dots (..). By using these symbols, a malicious user could craft a reverse path by chaining slashes and double dots, and therefore gain access to unauthorized files in the server if he/she somehow managed to know the directory structure on the server.

In this example, we can see an easy way to sanitize the input: the function str_replace(), which can remove all the slashes and all the sequences of dots from the filename. In the example we simulate that the user entered as filename a certain path "/../../../passwords.txt" or /../../../user/passwords.txt and after validation we return sanitized strings. If we want to preserve slashes that are placed within text, we can instead replace the substring "../" as seen in the second example. By doing this sanitization, we prevent the users from moving up in the directory hierarchy of the server.

<?php
// Processing "/../../../passwords.txt"
$_POST['filename'] = "/../../../passwords.txt";
$filename = str_replace('/', '', $_POST['filename']);
$filename = str_replace('..', '', $filename);
print 'str_replace() outputs: ' . htmlentities($filename);
print '<br/>';
$filename = str_replace('../', '', $_POST['filename']);
print 'str_replace() outputs: ' . htmlentities($filename);
print '<br/><br/>';
// Processing "/../../../user/passwords.txt"
$_POST['filename'] = "/../../../user/passwords.txt";
$filename = str_replace('/', '', $_POST['filename']);
$filename = str_replace('..', '', $filename);
print 'str_replace() outputs: ' . htmlentities($filename);
print '<br/>';
$filename = str_replace('../', '', $_POST['filename']);
print 'str_replace() outputs: ' . htmlentities($filename);
?>
str_replace() outputs: passwords.txt
str_replace() outputs: /passwords.txt

str_replace() outputs: userpasswords.txt
str_replace() outputs: /user/passwords.txt

PHP Example #110

Sanitizing filenames with realpath()

Another useful technique for sanitizing file names is the function realpath(), which automatically removes dangerous elements in the filename string. In this example, if $_POST['user'] were "james", then $filename would be /usr/local/data/james and the if() statement would be excuted, but if $_POST['user'] were something like ../passwords.txt, then $filename would be /usr/local/passwords.txt and the if() statement would return false, preventing access to the file.

<?php
$filename = realpath("/usr/local/data/$_POST['user']");
// Ensures that $filename is below /usr/local/data
// If substr() returns something different than "/usr/local/data"
// that means that the path has been manipulated
if ('/usr/local/data' == substr($filename, 0, 16)) {
print 'User profile for ' . htmlentities($_POST['user']) . ':
'; print file_get_contents($filename);
} else {
print "Invalid user entered.";
}
?>

PHP Example #111

Sending files through forms

There is a form element (type="file") that allows to send files easily to a server. The PHP interpreter grants access to the file sent through the auto-global array $_FILES[]. This example shows a form processing program whose validate_form() and process_function() functions use $_FILES[]. To make this script to work, you have to adjust with precision the path for your server, which in this example is represented by /uploads/. Please note that fatal errors are returned because I have intentionally disabled the demonstration of this example for obvious reasons.

<?php
if ($_POST['_stage']) {
// If validate_form() returns errores, pass them to show_form()
if ($form_errors = validate_form()) {
show_form($form_errors);
} else {
// The data sent is valid, then process it
process_form();
}
} else {
// The form had not been sent, then show it
show_form();
}
function show_form($errors = '') {
if ($errors) {
print 'You need to correct the following errors: <ul><li>';
print implode('</li><li>', $errors);
print '</li></ul>';
}
print '<form enctype="multipart/form-data" method="post" action="' . $_SERVER['PHP_SELF'] . '">';
print 'File to upload: <input name="my_file" type="file"/>';
print '<input type="hidden" name="MAX_FILE_SIZE" value="131072"/>';
print '<input type="hidden" name="_stage" value="1"/>';
print '<input type="submit" value="Upload"/>';
}
function validate_form() {
$errors = array();
if (($_FILES['my_file']['error'] == UPLOAD_ERR_INI_SIZE) ||
($_FILES['my_file']['error'] == UPLOAD_ERR_FORM_SIZE)) {
$errors[] = 'Uploaded file is too big.';
} elseif ($_FILES['my_file']['error'] == UPLOAD_ERR_PARTIAL) {
$errors[] = 'File upload was interrupted.';
} elseif ($_FILES['my_file']['error'] == UPLOAD_ERR_NO_FILE) {
$errors[] = 'No file uploaded.';
}
return $errors;
}
function process_form() {
print "You uploaded a file called {$_FILES['my_file']['name']} ";
print "of type {$_FILES['my_file']['type']} that is ";
print "{$_FILES['my_file']['size']} bytes long.\n";
$safe_filename = str_replace('/', '', $_FILES['my_file']['name']);
$safe_filename = str_replace('..', '', $safe_filename);
$destination_file = '/uploads/' . $safe_filename;
if (move_uploaded_file($_FILES['my_file']['tmp_name'], $destination_file)) {
print "Successfully saved file as $destination_file.";
} else {
print "Couldn't save file in /uploads/.";
}
}
?>
File to upload: