Tuesday, 10 May 2011

PHP - Cold file web site

Introduction

This blog describes my experience while performing a task assigned by my tutor. The task consists of creating a web site using PHP that includes the following features:
  • Users must be able to work with this program using a Web browser.
  • When accessing the web page, it should ask the user to enter a user name and password, and not allow the user to access their Web space unless they know the password.
  • Passwords should be stored in an appropriate MySQL database.
  • Once allowed access the user should be able to:
    • Create and delete directories
    • Delete files
    • Upload new files
    • Only be able to access their own space, which should have space limits imposed upon it.

Web project mapping

This section provides a brief overview on how the Cold file web project is constructed. The following diagram shows all involved web pages and how pages communicate with each other.

The utilities.php contains functions used from various pages. This page is included in all web pages using the include(<Page name>) PHP command. Amongst others this page contains functions that connect with the database back end and provides access to global variables.
The index.php enables the user to either create a new account or log-in using an existing account. Once a user is logged in, the user is redirected to home.php. This page provides the required tools and enables a user to manage his files and folders. All user requests submitted in home.php are submitted and processed by manage.php.
The settings.php page enables the user to change his account password and change the web page format. When a user selects a format, the format is stored in a cookie on the client machine. Whenever a page is launched, the cookie value is retrieved and the style format is changed according to the cookie value.

My Web project

Including code

To ease maintenance and eliminate repetitive code, I grouped all functions in a separate PHP page; utilities.php. To allow a web page to access and execute functions from the utilities.php page, a web page must implement the include(<Page name>) PHP syntax as follows:
//Include utilities
include ("utilities.php");
When a PHP file is included into a web page, the web page inherits all variables and functions contained in the PHP file. While using this syntax, I noticed that PHP functions (in the utilities page) could not access variables in the utilities page. For example, when the following code is called, a null value is returned:
$maxfileupload = 5242880;
function getMaximumFile(){
return $maxfileupload;
}
After a bit of research, I found out that to access a global variable you must use the Global keyword. Reason being that unless stated, the function will process $maxfileupload as a local variable and since it has not been assigned a value within the function scope, the function will return null.  To instruct a function that $maxfileupload is a global variable, the syntax should be changed as follows:
$maxfileupload = 5242880;
function getMaximumFile(){
Global $maxfileupload;
return $maxfileupload;
}

Log-in

The log-in page (index.php) contains the required log-in PHP script to validate a user account and redirect the user to the home page. The web page layout is shown in the screenshot below:
 
I added some JavaScript that checks if the user name and password are not empty before submitting the form. The following JavaScript will run on the client side:
If the name and password text boxes are left empty, the script will display an asterisk ‘*’ near the empty text box.
When the page is submitted, the user credentials are validated using the validateUser(<User name>,<Password>) function in the utilities page. The function is called using the following syntax:
$row=validateUser($_POST['txtUname'],md5($_POST['txtPassword']));
Where: txtUname is the user name text box value and txtPassword is the user password text box value.
NOTE: that the password is encrypted using MD5.
In line 21, the script is instructing the PHP engine to get the global variables; these are parameters used to connect to the database back end.
From line 23 to line 28, the script is performing the MySQL database connection and selecting the database to use.
In line 31 the SQL statement is created, this will retrieve the user details from tblusers table. The sprintf() function is used to create a formatted string, in this case, the %s will be replaced by the user name. For more information on sprintf, refer to sprintf manual.
The mysql_real_escape_string() function is used to comment any SQL syntax and prevent SQL injection. For example, one of the most common SQL injection syntax used in log-in pages is:
' or 'a'='a'--
When using the mysql_real_escape_string() this syntax is converted into the following text using the ‘\’ escape character; such text is not processed as SQL syntax:
\' or \'a\'=\'a\'--
Line 34 submits the SQL query and in line 38 the result is converted into an array using mysql_fetch_array(). In line 41 the password is compared, and if valid the array is returned.
If the user account is valid, a session with the user details is created using the following syntax and redirected to the home page:
//Store row in session
$_SESSION['user']=$row;

User Home page

The user home page (home.php) enables a logged-in user to manage his files and folders. The web page layout is shown in the screenshot below:

Open a folder

Users can click on a folder and open the contents. The current selected folder is stored in a session (selectedFolder) using the following code:
When the home page is launched, the session is retrieved and the files and folder contents are displayed in two separate list boxes.

Get files and folders

When the home page is launched, the folder selected by the user is stored in a session and the files and folder contents are stored in two arrays as follows:
//Get files and folders
$FolderList= getFolders($selectedFolder);
$FileList =getFiles($selectedFolder);
The PHP functions getFolders(<Folder path>) and getFiles(<Folder path>) are used to get the folder names and file names respectively. Both functions are very similar, I will describe the getFiles(<Folder path>) function:
In line 244, an empty array is created to store all file names. In line 246, the directory is opened using the @opendir(<Folder path>). The loop between lines 249 and 258 will iterate for each file/directory found in this location using readdir(<file>).
The if statement in line 251 is used to ignore the dots ”.” returned by the readdir() function.
The if statement in line 252 checks if the retrieved file is a directory. If the file is not a directory, the file is added to the array created in line 244.
The directory is closed in line 261 using closedir(<file>) and the array is returned in line 264.
The retrieved files and folders are displayed using two HTML <select> tags using a foreach loop that iterates through the retrieved values:
<select name="slcFolder" size="7" style="width:310px">
    <?php
        foreach($FolderList as $folder)
            echo("<option value=\"$folder\">$folder</option>");
    ?>
</select>

Create a folder

I included a text box and a button to enable the user to create a new folder. When the user keys in a folder name and clicks the button, the following PHP code is executed:
 
If the user clicked the button to create a new folder, the code in lines 25 and 45 are executed. Line 27 checks if a valid folder name was keyed in by the user; if not an error message is displayed.
In line 33, the selected folder is retrieved from a session and the new folder path is constructed.
Lines 34 and 37 are used to check if the folder already exists; if exists an error message is displayed.
If no errors occurred, the folder is created in line 40 using mkdir(<Folder path>);

Delete a folder

PHP will complain if you try to delete a folder that is not empty. To delete a folder you must create a recursive function that goes through the folder contents and deletes files one by one. To delete a folder, the following function is used:
This function will loop through all items and delete the contents. The following syntax is used to delete a file:
unlink(<File path>);
Folders are deleted using the following syntax:
rmdir(<Folder path>);
NOTE: A recursive function is a function that will continue to call itself until a stopping condition is met. In this case, the stopping condition is when a file is found.

Upload file

To enable a user to upload a file, the following HTML markup is used:
<input type="hidden" name="MAX_FILE_SIZE" value="5242880" />
<input type="file" name="fleUpload" size="17" />
<input type="submit" name="btnFleUpload" value="Upload Now" class="controlButton" onclick="return showProgress();"/>
The MAX_FILE_SIZE is used to set the maximum file size that can be uploaded (5MB).  The file input type enables the user to select the file to upload by displaying the following dialog:
Once a file is selected, the user has to click the ‘Upload Now’ button.
NOTE: Files are uploaded in a temporary folder on the server. Once uploaded, the PHP code will check if the file is valid and if valid the file is moved to its proper location.
Step 1 - Check Errors
When a file is uploaded, the selected file and all file information is added to $_FILES array. Errors that occurs while uploading are stored in  $_FILES ["fleUpload"] ["error"]. So before upload, I added the following syntax to check if errors occurred. This is done by retrieving the error code from the array. If an error occurred, the respective error message is displayed.
//Check for any errors
if ($_FILES["fleUpload"]["error"] > 0){

$errorcode=$_FILES["fleUpload"]["error"];

$errorText="";

if($errorcode==1 | $errorcode==2){
$errorText="File is too large.";
}else if($errorcode==4){
$errorText="No file selected.";
}else{
$errorText="General error.";
}

$postText ="Location:home.php?error=An error occured while uploading file. Error: ".$errorText;
header($postText);
die();
}
Step 2 - Check Blacklist
The next validation is to check if a file can be uploaded. This is done by comparing the file extension with a blacklist database table. If the extension exists in the table, the file is not allowed. The following syntax checks if a file extension is valid:
The extension is passed as a parameter to this function. Using an SQL select statement the row is retrieved from the database. If a row exists, the function returns the row data. If not, the function returns a null value.
In lines 184 and 185, the file name and file extension are retrieved. In lines 187 to 191 the code is checking if the file extension is null, and if null the page displays an error.
In line 193, using the isBlacklist(<File extension>) function, the SQL row is retrieved. If the row is not empty an error is displayed containing the extension description. The following screenshot shows how an executable (exe) is blocked:
Step 3 – Check file size
The following syntax is used to retrieve the uploaded file size in bytes:
$filesize=$_FILES['fleUpload']['size'];
The following syntax checks if the maximum uploading size has been exceeded:  
if($filesize>$maxfileupload){
//file is too large
header("Location:home.php?error=File is too large.");  
die();
}
Where: $maxfileupload is the maximum size that can be uploaded.
Step 4 – Check storage size
To get a folder size, a recursive method has to be created. The following method will loop through all files in a folder and will add up all file sizes. When finished the function returns the total folder size.
This function is used to check if the maximum storage space has been exceeded. The syntax is as follows:
if(($filesize+GetFolderSize($homeDirectory))>$maxstorage){
//Storage exceeds limit
header("Location:home.php?error=Your storage is full.");
die();
}
Step 5 – Check if the file exists
In this step, the PHP code first constructs the target file path using the basename() function as follows:
$target=$_SESSION["selectedFolder"].basename($_FILES['fleUpload']['name']);
Using the file_exists(<File path>) function, the PHP code checks if the file exists. If the file already exists, an error is displayed.
if (file_exists($target)){
header("Location:home.php?error=Cannot upload file, file already exists.");
die();
}
Step 6 – The upload
If no errors occur in the previous steps, the uploaded file is moved from the temporary location to its proper location. The following code will do the trick:
move_uploaded_file($_FILES["fleUpload"]["tmp_name"],$target);

Where: $_FILES["fleUpload"] ["tmp_name"] is the temporary folder path and $target is the destination were the file should be saved.

Download file

The following syntax is used to download a file from the web server:
In line 233, the PHP code is checking if the ‘Download’ button was clicked. In line 235, the code is checking if the user selected a file to download from the list box.
If a file was selected, the file path is constructed in line 237. In lines 238 to 248 using the header() function, an HTTP header is created that enables the user to download the selected file.

Move and paste

The home page enables a user to move files from one folder to another. The sequence is as follows:
  1. User selects a file from the file list.
  2. User clicks the ‘Move’ button.
  3. The selected file path is stored in a session (“moveFile”).
  4. The ‘Paste’ button is shown.
  5. User selects were to move the file and clicks ‘Paste’.
  6. If the move action is successful, the file is deleted from the original location.
The following PHP syntax, copies the file to the new location:
The copy(<Source>,<Destination>) is used in line 127 to copy the selected file. If errors are not encountered, the original file is deleted in line 128 using unlink(<file path>). The session is cleared in line 130. 

Settings page

The settings page (settings.php) enables the user to change the appearance theme (the CSS to use) and to change the user password. The user interface is as follows:
When the user clicks ‘Apply’ in the template section, the following PHP code is executed:
In line 21, using the setcookie(<Name>,<Value>,<Time>) a new cookie is created storing the template name. Each time a page is launched, the cookie value is retrieved and the respective CSS style is selected. The cookie will be valid for one year.
When the user clicks ‘Apply’ in the password section, the PHP code checks if both passwords were submitted and calls the following PHP code:
After connecting to the MySQL instance, the Update SQL statement is submitted with the new password.
NOTE: The password value is encrypted using MD5 and the user name is parsed using mysql_real_escape_string() function to prevent SQL injection.

Create new users

The newuser.php, is accessible from the index page and enables a user to create a new account. The user interface is as follows:
When a user clicks the ‘Submit’ button, the information supplied by the user is validated and if valid the information is submitted. Important to note, that the email address submitted by the user is validated using regular expressions as follows:
function isEmailValid($email){
if(preg_match("/^[_\.0-9a-zA-Z-]+@([0-9a-zA-Z][0-9a-zA-Z-]+\.)+[a-zA-Z]{2,6}$/i", $email))
return true;
else
return false;
}
If the email submitted matches the regular expression, the function returns true. If not the function returns false.
The following function is used to check if a user exists in the database:
If a user does not exist, and the keyed values are valid, the following code creates the new user account and the server folder; where files are saved:
NOTE: When a user account is created, a folder with the user name is created containing three folder; My Documents, My Music and My Pictures.

Change page style

The web project contains three different style sheets (CSS) and from the settings.php page, the user can select the style sheet to use for his profile.
To achieve this, each web page retrieves the style sheet name from a cookie, using the getStyle() function declared in the utilities page. The HTML markup is as follows:
<link rel="stylesheet" type="text/css" href="<?php echo(getStyle());?>"/>
The following PHP syntax is used to determine the style sheet to use:
In line 58, the default style is assigned to variable $style. In line 61, the script is checking if a cookie exists and if exists the code in lines 64 to 76 is executed. Using the switch syntax, the cookie value is retrieved and compared with the switch cases. In line 78 the switch result is returned.

Publish the web project

In this section I will describe the settings and configurations I did on my XAMPP server to publish my web project.

Server information

Before publishing the website, it is important to check that the server is configured to allow file uploads and the maximum uploading size that can be uploaded. To display this information one can use the phpinfo() function or the following script:
<?php

//If file upload is enabled, this displays '1'.
echo("File uploads: ".ini_get("file_uploads")."<BR>");

//This displays the maximum memory in MB that is allocated to the script.
echo("Memory limit: ".ini_get("memory_limit")."<BR>");

//This displays the maximum post data in MB that a script can post.
echo("Post maximum size: ".ini_get("post_max_size")."<BR>");

//This displays the maximum size in MB that can be uploaded.
echo("Maximum upload file size: ".ini_get("upload_max_filesize")."<BR>");

?>
This code block will generate the following output:

Configuration files

You can customize the PHP installation behavior through the php.ini configuration file. When the PHP engine is launched, the engine locates the php.ini file and reads it contents. The php.ini parameters are stored in the following format:
<Parameter name> = <Parameter value>
Empty lines and lines beginning with a semicolon ‘;’ are ignored. So to add a comment in a php.ini file, the following syntax is used:
;My Comment goes here
To nullify a parameter, you can either leave the value blank or type ‘none’; I recommend using ‘none’, because this will ease finding a nullified value. Example:
myparameter = none
To make your php.ini file easily to read, you can use sections. Sections are defined using square brackets ‘[]’ and the following is an example:
[Custom Settings]
Myparameter = myvalue
The php.ini location depends on how you installed PHP. Not to struggle to find this file, the easy way to find it is using the phpinfo() function. The php.ini file is displayed in the report as shown in the below image:

What to configure

To run this website project, you may need to change the following parameters in the php.ini file:
Parameter name
Default value
Description
memory_limit
128M
Set the maximum memory in MB that you want to allocate to the script. The ‘M’ is required.
post_max_size
8M
Set the maximum post data in MB that a script can post. The ‘M’ is required.
upload_max_filesize
5M
Set the maximum size in MB that can be uploaded. The ‘M’ is required.
file_uploads
on
Assign an ‘on’ value to enable file upload on your server.
upload_tmp_dir
"C:\WINDOWS\Temp"
Configure the location of your server temporary folder.

Copy the web project

To publish a web project in Apache, all web pages and files should be copied in the htdocs folder. The htdocs location depends on your Apache installation and the server operating system used. For example, when installing XAMPP the location is:
<Install directory>\XAMPP\htdocs

Configure global variables

When connecting to a database table, a function must provide a valid database user account. In this web project, all functions are grouped in the utilities.php file. This file contains a number of functions used within the web project and the global variables used within the project.
The following global variables must be changed according to your environment:
 Variable name
Variable description
Example
$mysqlinstance
The MySQL server location. This value can be an IP address.
$mysqlinstance = "localhost";
$mysqluser
The MySQL user account used to execute SQL commands. This user must have at least the following permissions:
  • Insert
  • Update
  • Select
For more information on how to manage user accounts, refer to Adding User Accounts.
$mysqluser="webuser";
$mysqlpassword
The MySQL user account password.
$mysqlpassword="12345";
$mysqldatabase
The MySQL database name containing the system tables.
$mysqldatabase="coldfile";
$mainpath
The file server location, were users will store files and folders.
Upon creating a new user, a folder is created in this location.
$mainpath="C:\\coldfile\\";
$maxfileupload
The maximum file size that a user can upload in bytes.
$maxfileupload = 5242880;
$maxstorage
The maximum storage space allowed for a user.
$maxstorage = 52428800;

Database configuration

The database back end should contain the following tables:
Table Name
tblblacklist
Description
Contains a list of blacklisted file extensions. File extensions added in this table will be blocked when uploaded.
Column Name
Data type
Size
Null
fldExtension
VARCHAR
5
Not Null (PK)
fldDescription
VARCHAR
45
Not Null



Table Name
tblusers
Description
Contains the list of users that can log-in and use the system.
Column Name
Data type
Size
Null
fldUserName
VARCHAR
20
Not Null (PK)
fldName
VARCHAR
50
Not Null
fldSurname
VARCHAR
50
Not Null
fldPhone
VARCHAR
15
Null
fldEmail
VARCHAR
20
Not Null
fldPassword
VARCHAR
50
Not Null

To ease a bit the database configuration, I included a MySQL dump file with the web project.  For more information on how to import MySQL dump files, refer to A Database Backup Program.

Testing

Create a new user

Adding a user that already exists
Adding a new user with invalid email address

Index page

Submit invalid credentials

Home page

Create a new folder
Download a file
Upload a file
Move a file
Delete a file with JavaScript confirm delete

Settings Page

Change themes

Test on mobile browser

To test my web project on a mobile browser, I downloaded and installed Opera mini simulator. This simulator provides all functions found in a mobile Internet browser. The following screenshots shows how my application will be viewed if accessed from a mobile phone:
The index page
The home page
Download a file
Delete a file
Change themes
Select a file to upload
Create a folder

Conclusion

While testing this web project, I learned how easy it is to debug a web page when using XAMPP and Notepad++. In XAMPP I shared my htdocs folder to be accessible over an FTP connection. Using NppFTP (A Notepad++ plug in) I could easily update my web pages contents and upload them directly to the htdocs folder using the FTP protocol.
When searching the web for PHP code examples, I found very useful the PHP manual. This site describes many of the functions mentioned in this blog and provides also some useful examples.
Another useful application I bumped into while doing this project is Opera mini simulator. This application enables you to view a web site on a mobile phone web browser. The good news is that you do not have to own a smart phone to test your web site on mobile phone.

Happy coding…

Resources

Download the cold file project files from Windows Live.

1 comment: