Secure Online File Storage PHP, MySQL

👤 Sharing: AI
```php
<?php

// --- Configuration ---
define('UPLOAD_DIR', 'uploads/'); // Directory to store uploaded files
define('MAX_FILE_SIZE', 5 * 1024 * 1024); // Maximum file size (5MB)
define('ALLOWED_EXTENSIONS', ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx']); // Allowed file extensions

// --- Database Configuration ---
define('DB_HOST', 'localhost');
define('DB_USER', 'your_db_user');
define('DB_PASSWORD', 'your_db_password');
define('DB_NAME', 'file_storage');

// --- Helper Functions ---

/**
 * Connect to the database.
 *
 * @return mysqli|false  mysqli connection object or false on failure.
 */
function db_connect() {
    $conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }
    return $conn;
}


/**
 * Sanitize user input to prevent SQL injection.
 *
 * @param mysqli $conn   Database connection object.
 * @param string $data   The data to sanitize.
 *
 * @return string        The sanitized data.
 */
function sanitize_input($conn, $data) {
    return mysqli_real_escape_string($conn, trim($data));
}

/**
 * Generate a unique filename to avoid conflicts.
 *
 * @param string $filename  The original filename.
 *
 * @return string           A unique filename.
 */
function generate_unique_filename($filename) {
    $extension = pathinfo($filename, PATHINFO_EXTENSION);
    $basename = pathinfo($filename, PATHINFO_FILENAME); // get filename without extension
    return uniqid($basename . '_') . '.' . $extension;
}


/**
 * Check if a file extension is allowed.
 *
 * @param string $filename  The filename to check.
 *
 * @return bool            True if the extension is allowed, false otherwise.
 */
function is_allowed_extension($filename) {
    $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    return in_array($extension, ALLOWED_EXTENSIONS);
}


// --- Upload Handling ---

if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES["fileToUpload"])) {

    $file = $_FILES["fileToUpload"];
    $filename = $file["name"];
    $filesize = $file["size"];
    $filetmp = $file["tmp_name"];
    $fileError = $file["error"]; // File upload error code


    // --- Error Handling ---
    $errors = [];

    if ($fileError !== UPLOAD_ERR_OK) {
        switch ($fileError) {
            case UPLOAD_ERR_INI_SIZE:
                $errors[] = "File is too large (exceeds upload_max_filesize in php.ini).";
                break;
            case UPLOAD_ERR_FORM_SIZE:
                $errors[] = "File is too large (exceeds MAX_FILE_SIZE specified in the HTML form).";
                break;
            case UPLOAD_ERR_PARTIAL:
                $errors[] = "File was only partially uploaded.";
                break;
            case UPLOAD_ERR_NO_FILE:
                $errors[] = "No file was uploaded.";
                break;
            case UPLOAD_ERR_NO_TMP_DIR:
                $errors[] = "Missing a temporary folder.";
                break;
            case UPLOAD_ERR_CANT_WRITE:
                $errors[] = "Failed to write file to disk.";
                break;
            case UPLOAD_ERR_EXTENSION:
                $errors[] = "File upload stopped by extension.";
                break;
            default:
                $errors[] = "An unknown error occurred during file upload.";
        }
    }

    if ($filesize > MAX_FILE_SIZE) {
        $errors[] = "File is too large (maximum " . MAX_FILE_SIZE / (1024 * 1024) . "MB).";
    }

    if (!is_allowed_extension($filename)) {
        $errors[] = "Invalid file extension. Allowed extensions are: " . implode(", ", ALLOWED_EXTENSIONS);
    }

    // --- Upload Process ---

    if (empty($errors)) {
        $uniqueFilename = generate_unique_filename($filename);
        $destination = UPLOAD_DIR . $uniqueFilename;

        if (move_uploaded_file($filetmp, $destination)) {
            // File uploaded successfully. Now save the file information to the database.

            $conn = db_connect();
            if ($conn) {
                $originalFilename = sanitize_input($conn, $filename); // Sanitize original filename
                $uniqueFilenameDB = sanitize_input($conn, $uniqueFilename); // Sanitize unique filename

                $sql = "INSERT INTO files (original_filename, unique_filename, upload_date) VALUES ('$originalFilename', '$uniqueFilenameDB', NOW())";

                if ($conn->query($sql) === TRUE) {
                    $upload_message =  "File uploaded successfully! <a href='" . htmlspecialchars($destination) . "' target='_blank'>View File</a>"; // Escape the URL
                } else {
                    $errors[] = "Error saving file information to the database: " . $conn->error;
                    //It's important to delete the uploaded file here if database insertion fails.
                    unlink($destination); // Delete the uploaded file to maintain data integrity
                }
                $conn->close();
            } else {
                $errors[] = "Could not connect to the database.";
                 //It's important to delete the uploaded file here if database insertion fails.
                unlink($destination); // Delete the uploaded file to maintain data integrity

            }

        } else {
            $errors[] = "Error moving the uploaded file.";
        }
    }
}

?>

<!DOCTYPE html>
<html>
<head>
    <title>Secure File Upload</title>
    <style>
        body { font-family: sans-serif; }
        .error { color: red; }
        .success { color: green; }
    </style>
</head>
<body>

    <h1>Secure File Upload</h1>

    <?php if (!empty($errors)): ?>
        <div class="error">
            <ul>
                <?php foreach ($errors as $error): ?>
                    <li><?php echo htmlspecialchars($error); ?></li>  <!-- Escape for security -->
                <?php endforeach; ?>
            </ul>
        </div>
    <?php endif; ?>

    <?php if (isset($upload_message)): ?>
        <div class="success">
            <?php echo $upload_message; ?>
        </div>
    <?php endif; ?>


    <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post" enctype="multipart/form-data">  <!-- Escape for security -->
        Select file to upload:
        <input type="file" name="fileToUpload" id="fileToUpload">
        <input type="submit" value="Upload File" name="submit">
    </form>


    <h2>Uploaded Files</h2>

    <?php
    // Display the list of uploaded files from the database
    $conn = db_connect();
    if ($conn) {
        $sql = "SELECT * FROM files ORDER BY upload_date DESC";
        $result = $conn->query($sql);

        if ($result->num_rows > 0) {
            echo "<ul>";
            while ($row = $result->fetch_assoc()) {
                $filepath = UPLOAD_DIR . $row["unique_filename"]; // Construct the file path
                echo "<li>";
                echo htmlspecialchars($row["original_filename"]) . " - Uploaded on: " . htmlspecialchars($row["upload_date"]);  // Escape for security
                if (file_exists($filepath)) {
                    echo " - <a href='" . htmlspecialchars($filepath) . "' target='_blank'>View</a>"; // Escape for security
                } else {
                    echo " - File not found on server."; // Handle the case where the file is missing
                }
                echo "</li>";
            }
            echo "</ul>";
        } else {
            echo "No files uploaded yet.";
        }
        $conn->close();
    } else {
        echo "Could not connect to the database to retrieve file list.";
    }
    ?>

</body>
</html>
```

**Explanation:**

1. **Configuration:**
   - `UPLOAD_DIR`:  Specifies the directory where uploaded files will be stored.  It's crucial to make sure this directory exists and is writable by the web server user (e.g., `www-data` on Ubuntu/Debian or `apache` on CentOS/RHEL).  It is best practice to locate this directory *outside* the web server's document root to prevent direct access to the uploaded files.  If that's not possible, use `.htaccess` (Apache) or similar configurations to restrict direct access.
   - `MAX_FILE_SIZE`: Sets the maximum allowed file size in bytes.  It's a good idea to set this to match the `upload_max_filesize` and `post_max_size` settings in your `php.ini` file.
   - `ALLOWED_EXTENSIONS`: An array of allowed file extensions.  This helps prevent users from uploading malicious files (e.g., executable PHP scripts).
   - `DB_HOST`, `DB_USER`, `DB_PASSWORD`, `DB_NAME`:  Database connection credentials.  **Important:** Replace these placeholders with your actual database information.

2. **Helper Functions:**
   - `db_connect()`: Establishes a connection to the MySQL database.  It returns a MySQLi connection object or `false` on failure.
   - `sanitize_input($conn, $data)`: Sanitizes user input using `mysqli_real_escape_string()` to prevent SQL injection vulnerabilities.  This is *essential* to protect your database.  Always sanitize any data that comes from the user before using it in a database query.
   - `generate_unique_filename($filename)`: Generates a unique filename using `uniqid()` to avoid filename collisions.  This is important because you don't want users to overwrite each other's files. It also prepends the original filename to the unique ID, making it slightly easier to identify the file later.
   - `is_allowed_extension($filename)`: Checks if a file's extension is in the `ALLOWED_EXTENSIONS` array.

3. **Upload Handling (Inside `if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES["fileToUpload"]))`)**
   - **Checks for POST request and file presence:** Verifies that the form was submitted using the POST method and that a file was actually uploaded using `isset($_FILES["fileToUpload"])`.
   - **Retrieves File Information:**  Gets the file name, size, temporary location, and any error codes from the `$_FILES` array.
   - **Error Handling:**
     - Checks for file upload errors using `$file["error"]`. The `$file["error"]` value will be `UPLOAD_ERR_OK` (value 0) if the upload was successful and have one of the other `UPLOAD_ERR_*` constants if there was an error.  The code handles common upload errors like file size limits, partial uploads, missing temporary directory, and extension issues.  Provides specific error messages to the user.
     - Checks for file size exceeding `MAX_FILE_SIZE`.
     - Checks for invalid file extensions.
   - **Upload Process (Inside `if (empty($errors))`)**
     - **Generates Unique Filename:** Creates a unique filename for the uploaded file.
     - **Defines Destination:**  Specifies the full path to where the file will be saved.
     - **`move_uploaded_file()`:**  Moves the uploaded file from its temporary location to the specified destination.  **Security Note:** This function is crucial. It verifies that the uploaded file was actually uploaded through the HTTP POST mechanism.  Don't skip this step!
     - **Database Interaction:**
       - Connects to the database using `db_connect()`.
       - Sanitizes the original filename and the unique filename using `sanitize_input()` *before* inserting them into the database. This prevents SQL injection.
       - Executes an `INSERT` query to store the file information (original filename, unique filename, and upload date) in the `files` table.  **Important:** You'll need to create this table in your MySQL database. See the table structure below.
       - Handles database errors gracefully.  If the database insertion fails, the code now *deletes the uploaded file* using `unlink($destination)` to maintain data consistency.  This prevents orphaned files on the server if the database record isn't created.
       - Displays a success message with a link to the uploaded file.
     - **Handles `move_uploaded_file()` failure:**  Displays an error message if the file could not be moved.

4. **HTML Form:**
   - A simple HTML form with a file input (`<input type="file" name="fileToUpload">`) and a submit button.
   - `enctype="multipart/form-data"`:  **Crucial!**  This attribute is required for forms that upload files.
   - The `action` attribute points to the current script (`<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>`). `htmlspecialchars()` escapes the string to prevent XSS vulnerabilities.
   - The HTML form includes error message display and a success message section.

5. **Displaying Uploaded Files:**
   - The script retrieves file information from the `files` table using a `SELECT` query.
   - It iterates through the results and displays the original filename, upload date, and a link to the file.  The link uses `htmlspecialchars()` to escape the URL, preventing potential XSS issues.
   - Includes a check using `file_exists()` to verify that the uploaded file actually exists on the server before creating a download link.  This is important in case files have been deleted manually from the server but the database record still exists.
   - Displays a message if no files have been uploaded yet or if there is a database connection error.

**Database Table Structure (MySQL):**

```sql
CREATE TABLE files (
    id INT AUTO_INCREMENT PRIMARY KEY,
    original_filename VARCHAR(255) NOT NULL,
    unique_filename VARCHAR(255) NOT NULL,
    upload_date DATETIME NOT NULL
);
```

**Key Security Considerations:**

* **SQL Injection Prevention:**  The code uses `mysqli_real_escape_string()` to sanitize user input *before* inserting it into the database. This is critical to prevent SQL injection attacks.
* **Cross-Site Scripting (XSS) Prevention:**  The code uses `htmlspecialchars()` to escape output displayed in the HTML, preventing XSS vulnerabilities.  This is particularly important when displaying filenames or other user-provided data.
* **File Extension Validation:**  The code checks the file extension against a list of allowed extensions.  This helps prevent users from uploading malicious files (e.g., PHP scripts, executable files).
* **Unique Filenames:** Generating unique filenames prevents users from overwriting each other's files.
* **`move_uploaded_file()`:**  Using `move_uploaded_file()` ensures that the uploaded file was actually uploaded through the HTTP POST mechanism.  This prevents malicious users from trying to trick the server by directly manipulating the file system.
* **File Storage Location:** Store uploaded files *outside* of the web server's document root whenever possible.  This prevents direct access to the files. If that's not possible, use `.htaccess` or similar configurations to restrict access to the `UPLOAD_DIR`.
* **Permissions:** Ensure that the `UPLOAD_DIR` has the correct permissions so that the web server user can write to it, but other users cannot.  (e.g., `chown www-data:www-data uploads/` and `chmod 755 uploads/` on Ubuntu/Debian).
* **Error Handling:**  The code handles file upload errors and database errors gracefully, providing informative messages to the user.  Avoid displaying sensitive information (e.g., database credentials, file paths) in error messages in a production environment.
* **File Size Limits:**  Enforce file size limits both in the PHP code and in your `php.ini` configuration.
* **Content Security Policy (CSP):**  Implement a Content Security Policy to further restrict what resources the browser is allowed to load, mitigating XSS attacks.
* **Regular Security Audits:**  Periodically review your code and configuration for potential security vulnerabilities.

**To run this example:**

1.  **Database Setup:**
    *   Create a MySQL database named `file_storage`.
    *   Create the `files` table using the SQL code provided above.
    *   Update the `DB_HOST`, `DB_USER`, `DB_PASSWORD`, and `DB_NAME` constants in the PHP code with your database credentials.

2.  **Create Upload Directory:**
    *   Create a directory named `uploads` in the same directory as your PHP script.
    *   Make sure the web server user (e.g., `www-data` or `apache`) has write permissions to this directory.

3.  **Save the Code:**
    *   Save the PHP code as a `.php` file (e.g., `upload.php`).

4.  **Access in Browser:**
    *   Place the `upload.php` file in your web server's document root (e.g., `/var/www/html/`).
    *   Access the script in your web browser (e.g., `http://localhost/upload.php`).

5.  **Configure `php.ini` (Important):**
    *   Edit your `php.ini` file (usually located in `/etc/php/[version]/apache2/php.ini` or `/etc/php/[version]/cli/php.ini` on Linux systems).
    *   Make sure the following settings are appropriate for your needs:
        *   `upload_max_filesize`:  Set the maximum allowed file size for uploads (e.g., `upload_max_filesize = 10M`).
        *   `post_max_size`:  Set the maximum size of POST data that PHP will accept (e.g., `post_max_size = 10M`).  This should be greater than or equal to `upload_max_filesize`.
        *   `file_uploads = On`: Ensure that file uploads are enabled.
        *   `upload_tmp_dir`:  Make sure this points to a valid temporary directory that PHP can use to store uploaded files before they are moved to their final destination.
    *   Restart your web server (e.g., `sudo systemctl restart apache2` or `sudo systemctl restart nginx`).

Remember to replace the placeholder database credentials with your actual credentials. Also, thoroughly test your upload script to ensure that it handles different file types, sizes, and error conditions correctly.  Always prioritize security best practices when handling file uploads.
👁️ Viewed: 7

Comments