Seeds of Web Success

Debugging File Uploads

File uploading in the context of a web server involves a series of steps, some of which are interconnected and some that work independently. Let’s delve into the process:

  1. Form Submission
    • A user selects a file via the file input element on the form.
    • Upon form submission, the browser sends a request to the server. This request, specifically for file uploads, is typically a POST request encoded as multipart/form-data.
    • The chosen file is sent in chunks as a part of the request payload.
  2. Web Server Handling (Apache in this case)
    • The Apache server receives the incoming POST request.
    • Apache then processes the headers and the body of the request. For file uploads, Apache must handle the multipart/form-data encoding and segregate the different parts of the payload.
    • The uploaded file, while still in the process of being received, gets stored in a temporary location (typically /tmp in UNIX-like systems or a temp directory on Windows systems). This is not yet the final location; it’s a staging area of sorts to hold the file data as it’s streamed in.
  3. PHP’s Role
    • After the entire file is received and stored temporarily, PHP is invoked by Apache. The specifics of this depend on how PHP is integrated into Apache (e.g., mod_php or FastCGI).
    • PHP starts executing the script associated with the endpoint (in our case, upload.php).
    • PHP populates the $_FILES superglobal array. This array contains metadata about the file like its name, MIME type, the temporary storage location, etc.
    • In the PHP script, when you access $_FILES["fileToUpload"]["tmp_name"], you’re referencing the temporary location of the uploaded file.
    • The file remains in this temporary location until the PHP script finishes executing, unless it’s moved using functions like move_uploaded_file().
    • If you don’t move the uploaded file in your PHP script, the file will be automatically deleted from the temporary location after the script finishes executing.
  4. Completing the Upload in PHP
    • Using the move_uploaded_file() function, the PHP script moves the file from the temporary location to the desired location (e.g., an uploads directory).
    • If the move is successful, the file now resides in its final location. If not, as mentioned earlier, the file in the temporary location is discarded once the script completes.
  5. Response
    • PHP can send a response back to the client, which could be a confirmation message, an error, or any other relevant information.
    • This response is then received by the client’s browser, which can then act on it (e.g., displaying a success message or handling errors in JavaScript).

Throughout this process, both Apache and PHP handle potential errors or issues. For instance, if the uploaded file exceeds a size limit (as set in php.ini via the upload_max_filesize directive), PHP will throw an error, and $_FILES["fileToUpload"]["error"] will provide an error code.

In summary, the file is received and managed by Apache first, stored temporarily, and then handed over to PHP for further processing. PHP then decides the fate of the file – whether to keep and move it to a final location or to let it be automatically discarded.

Certainly! Implementing a shutdown function is a good way to catch fatal errors that can halt the execution of a PHP script. Here’s how you can integrate a shutdown handler into the upload.php to catch errors and log them:

php

<?php

function writeToLog($message) {
$logFile = “debug.log”;
file_put_contents($logFile, date(“Y-m-d H:i:s”) . “: “ . $message . “\n”, FILE_APPEND);
}

function shutdownHandler() {
$last_error = error_get_last();
if ($last_error && ($last_error[‘type’] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR))) {
writeToLog(“Shutdown due to fatal error. Message: “ . $last_error[‘message’] . ” in file: “ . $last_error[‘file’] . ” on line: “ . $last_error[‘line’]);
}
}

register_shutdown_function(‘shutdownHandler’);

$target_dir = “uploads/”;
$target_file = $target_dir . basename($_FILES[“fileToUpload”][“name”]);
$imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));

writeToLog(“Upload Attempt Initiated”);

if (isset($_FILES[“fileToUpload”])) {
writeToLog(“Upload Temp Name: “ . $_FILES[“fileToUpload”][“tmp_name”]);
writeToLog(“Upload Type: “ . $_FILES[“fileToUpload”][“type”]);
writeToLog(“Upload Size: “ . ($_FILES[“fileToUpload”][“size”] / 1024) . ” kB”);
writeToLog(“Upload Name: “ . $_FILES[“fileToUpload”][“name”]);
writeToLog(“Target File: “ . $target_file);
writeToLog(“File Type: “ . $imageFileType);
} else {
writeToLog(“No file uploaded or file array not set in POST data.”);
exit(“No file uploaded.”);
}

if (move_uploaded_file($_FILES[“fileToUpload”][“tmp_name”], $target_file)) {
writeToLog(“The file “ . basename($_FILES[“fileToUpload”][“name”]) . ” has been uploaded successfully.”);
echo “The file “ . basename($_FILES[“fileToUpload”][“name”]) . ” has been uploaded.”;
} else {
writeToLog(“Error moving uploaded file.”);
echo “Sorry, there was an error uploading your file.”;
}
?>

The shutdownHandler function will be called when the script execution is finished or terminated. If there was a fatal error, the error will be logged.

This will ensure that even if your script encounters a fatal error, you’ll have a log of it. This is especially useful for debugging those issues that don’t throw exceptions but rather halt the script altogether.

Sure, I can help with that. Here’s a basic file upload system in PHP with HTML and some debugging using jQuery. This will allow you to inspect various stages of the process:

  1. HTML for the file upload form:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<title>Simple File Upload</title>
</head>
<body>
<form action=“upload.php” method=“post” enctype=“multipart/form-data”>
Select file to upload:
<input type=“file” name=“fileToUpload” id=“fileToUpload”>
<input type=“submit” value=“Upload File” name=“submit”>
</form><div id=“debugInfo”></div>

<script>
$(document).ready(function(){
$(“form”).submit(function(event){
event.preventDefault();

var formData = new FormData($(this)[0]);

$.ajax({
url: ‘upload.php’,
type: ‘POST’,
data: formData,
async: true,
cache: false,
contentType: false,
processData: false,
success: function(response){
$(“#debugInfo”).html(response);
},
error: function(){
$(“#debugInfo”).html(“Error during AJAX call.”);
}
});

return false;
});
});
</script>

</body>
</html>

  1. PHP for the file upload (upload.php):
php
<?php
$target_dir = "uploads/";
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 1;
$imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));
echo “Debugging information: <br>”;
echo “Upload Temp Name: “ . $_FILES[“fileToUpload”][“tmp_name”] . “<br>”;
echo “Upload Type: “ . $_FILES[“fileToUpload”][“type”] . “<br>”;
echo “Upload Size: “ . ($_FILES[“fileToUpload”][“size”] / 1024) . ” kB<br>”;
echo “Upload Name: “ . $_FILES[“fileToUpload”][“name”] . “<br>”;
echo “Target File: “ . $target_file . “<br>”;
echo “File Type: “ . $imageFileType . “<br>”;if (move_uploaded_file($_FILES[“fileToUpload”][“tmp_name”], $target_file)) {
echo “The file “ . basename($_FILES[“fileToUpload”][“name”]) . ” has been uploaded.”;
} else {
echo “Sorry, there was an error uploading your file.”;
}
?>

Make sure the uploads/ directory exists and is writable by the web server. If it’s not, the file move operation will fail.

Note: This is a basic file upload mechanism and may have vulnerabilities such as not checking the type of the file uploaded. In a production system, you’d want to add checks to ensure only allowed file types are uploaded, and there are no path traversal vulnerabilities or other types of vulnerabilities. The above code is primarily for debugging purposes.

 


The tmp dir is supposed to be listed in phpinfo() as upload_tmp_dir; however, this variable is often empty when using cPanel. The default in cPanel is for files to upload to /tmp, which is a folder owned by root, but with 777 permissions.


Watch the file size increment as the upload progresses:

root@18-209-62-97:/tmp/systemd-private-5159075e5a84470cba847c21ff468618-ea-php81-php-fpm.service-LYhdHg/tmp# ls -la
total 49952
drwxrwxrwt 2 root root 4096 Sep 8 22:53 .
drwx—— 3 root root 4096 Aug 9 15:19 ..
-rw——- 1 stoughtonhealth stoughtonhealth 51082501 Sep 8 23:07 phpjWqXsf

root@18-209-62-97:/tmp/systemd-private-5159075e5a84470cba847c21ff468618-ea-php81-php-fpm.service-LYhdHg/tmp# ls -la
total 53968
drwxrwxrwt 2 root root 4096 Sep 8 22:53 .
drwx—— 3 root root 4096 Aug 9 15:19 ..
-rw——- 1 stoughtonhealth stoughtonhealth 55193058 Sep 8 23:11 phpjWqXsf