Skip to main content
Bumped by Community user
Bumped by Community user
Bumped by Community user
Bumped by Community user
deleted 23 characters in body; edited tags; edited title
Source Link
Jamal
  • 34.9k
  • 13
  • 133
  • 237

PHP chunked Chunked file upload

So I made my own, really basic, upload script that allows file upload, using chunked request or not (only one request).

Here's my code :

I'd be curious to know your insight about it, mostly if there isare any security issues I missed.

PHP chunked file upload

So I made my own, really basic, upload script that allows file upload, using chunked request or not (only one request).

Here's my code :

I'd be curious to know your insight about it, mostly if there is any security issues I missed.

Chunked file upload

I made my own, really basic, upload script that allows file upload, using chunked request or not (only one request).

I'd be curious to know your insight about it, mostly if there are any security issues I missed.

Added constraint file size limit on code
Source Link
Cyril N.
  • 235
  • 1
  • 4
  • 16
<?php
define('MAX_FILE_SIZE_UPLOAD', 10000000); // 10Mb
header('Content-Type: application/json');

// Make sure file is not cached (as it happens for example on iOS devices)
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');

function validationFail($result, $title = 'Invalid request.', $code = 400) {
    header('HTTP/1.1 '.$code.' '.$title);
    echo(json_encode(array(
        'success' => false,
        'result'  => $result,
        'error'   => $title
    )));

    exit();
}

// We make sure no error occured
if (empty($_FILES['file']) || intval($_FILES['file']['error']) > 0) {
    validationFail('bad_request', 'An error occured.');
}

// We only allow CSV files. Of course, you can adapt this to your need
if ($_FILES['file']['type'] !== 'text/csv') {
    @unlink($_FILES['file']['tmp_name']);
    validationFail('bad_request', 'Please upload a CSV file.');
}

$tmpName = $_FILES['file']['tmp_name'];
$tmpDirectory = ini_get('upload_tmp_dir') ? ini_get('upload_tmp_dir') : sys_get_temp_dir();

// Security verification
if (!is_uploaded_file($tmpName)) {
    validationFail('bad_request', 'An error occured.');
}

// We start a session.
// This is required in order to track the sender of a file (not mix request)
if (session_status() == PHP_SESSION_NONE) {
    session_start();
}

if (isset($_SERVER['HTTP_CONTENT_RANGE']) && !empty($_SERVER['HTTP_CONTENT_RANGE']) && isset($_SERVER['CONTENT_LENGTH']) && is_numeric($_SERVER['CONTENT_LENGTH'])) {
    // We get the size of the file uploaded from the client (real, final size)
    $filesize = intval(substr($_SERVER['HTTP_CONTENT_RANGE'], strpos($_SERVER['HTTP_CONTENT_RANGE'], '/') + 1));

    if ($filesize > MAX_FILE_SIZE_UPLOAD) {
        validationFail('bad_request', 'File size allowed must be lower than 10Mb.');
    }

    if (isset($_SESSION['filename']) && is_file($tmpDirectory.'/'.$_SESSION['filename'])) {
        file_put_contents($tmpDirectory.'/'.$_SESSION['filename'], fopen($tmpName, 'r'), FILE_APPEND);
        unlink($tmpName); // We delete the file once we copied it, in order to not use unecessary storage

        // We stop here if the file is not completely loaded
        $currentSize = filesize($tmpDirectory.'/'.$_SESSION['filename']);
        if ($currentSize < $filesize) {
            exit('{"size": '.$currentSize.'}');
        } else {
            $tmpName = $tmpDirectory.'/'.$_SESSION['filename'];
        }
    } else {
        $_SESSION['filename'] = uniqid().'.csv.part';
        move_uploaded_file($tmpName, $tmpDirectory.'/'.$_SESSION['filename']);
        exit('{"size": '.filesize($tmpDirectory.'/'.$_SESSION['filename']).'}');
    }
}

// The file has been completely uploaded.
// You can do whatever you want now
// Like moving the file, which is located in $tmpName

// Note: don't use move_uploaded_file because the chuncked part created a new file and this function will fail.

rename($tmpName, dirname(__FILE__).'/files/'.uniqid().'.csv');
exit('{"success": true}');
<?php
header('Content-Type: application/json');

// Make sure file is not cached (as it happens for example on iOS devices)
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');

function validationFail($result, $title = 'Invalid request.', $code = 400) {
    header('HTTP/1.1 '.$code.' '.$title);
    echo(json_encode(array(
        'success' => false,
        'result'  => $result,
        'error'   => $title
    )));

    exit();
}

// We make sure no error occured
if (empty($_FILES['file']) || intval($_FILES['file']['error']) > 0) {
    validationFail('bad_request', 'An error occured.');
}

// We only allow CSV files. Of course, you can adapt this to your need
if ($_FILES['file']['type'] !== 'text/csv') {
    @unlink($_FILES['file']['tmp_name']);
    validationFail('bad_request', 'Please upload a CSV file.');
}

$tmpName = $_FILES['file']['tmp_name'];
$tmpDirectory = ini_get('upload_tmp_dir') ? ini_get('upload_tmp_dir') : sys_get_temp_dir();

// Security verification
if (!is_uploaded_file($tmpName)) {
    validationFail('bad_request', 'An error occured.');
}

// We start a session.
// This is required in order to track the sender of a file (not mix request)
if (session_status() == PHP_SESSION_NONE) {
    session_start();
}

if (isset($_SERVER['HTTP_CONTENT_RANGE']) && !empty($_SERVER['HTTP_CONTENT_RANGE']) && isset($_SERVER['CONTENT_LENGTH']) && is_numeric($_SERVER['CONTENT_LENGTH'])) {
    // We get the size of the file uploaded from the client (real, final size)
    $filesize = intval(substr($_SERVER['HTTP_CONTENT_RANGE'], strpos($_SERVER['HTTP_CONTENT_RANGE'], '/') + 1));

    if (isset($_SESSION['filename']) && is_file($tmpDirectory.'/'.$_SESSION['filename'])) {
        file_put_contents($tmpDirectory.'/'.$_SESSION['filename'], fopen($tmpName, 'r'), FILE_APPEND);
        unlink($tmpName); // We delete the file once we copied it, in order to not use unecessary storage

        // We stop here if the file is not completely loaded
        $currentSize = filesize($tmpDirectory.'/'.$_SESSION['filename']);
        if ($currentSize < $filesize) {
            exit('{"size": '.$currentSize.'}');
        } else {
            $tmpName = $tmpDirectory.'/'.$_SESSION['filename'];
        }
    } else {
        $_SESSION['filename'] = uniqid().'.csv.part';
        move_uploaded_file($tmpName, $tmpDirectory.'/'.$_SESSION['filename']);
        exit('{"size": '.filesize($tmpDirectory.'/'.$_SESSION['filename']).'}');
    }
}

// The file has been completely uploaded.
// You can do whatever you want now
// Like moving the file, which is located in $tmpName

// Note: don't use move_uploaded_file because the chuncked part created a new file and this function will fail.

rename($tmpName, dirname(__FILE__).'/files/'.uniqid().'.csv');
exit('{"success": true}');
<?php
define('MAX_FILE_SIZE_UPLOAD', 10000000); // 10Mb
header('Content-Type: application/json');

// Make sure file is not cached (as it happens for example on iOS devices)
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');

function validationFail($result, $title = 'Invalid request.', $code = 400) {
    header('HTTP/1.1 '.$code.' '.$title);
    echo(json_encode(array(
        'success' => false,
        'result'  => $result,
        'error'   => $title
    )));

    exit();
}

// We make sure no error occured
if (empty($_FILES['file']) || intval($_FILES['file']['error']) > 0) {
    validationFail('bad_request', 'An error occured.');
}

// We only allow CSV files. Of course, you can adapt this to your need
if ($_FILES['file']['type'] !== 'text/csv') {
    @unlink($_FILES['file']['tmp_name']);
    validationFail('bad_request', 'Please upload a CSV file.');
}

$tmpName = $_FILES['file']['tmp_name'];
$tmpDirectory = ini_get('upload_tmp_dir') ? ini_get('upload_tmp_dir') : sys_get_temp_dir();

// Security verification
if (!is_uploaded_file($tmpName)) {
    validationFail('bad_request', 'An error occured.');
}

// We start a session.
// This is required in order to track the sender of a file (not mix request)
if (session_status() == PHP_SESSION_NONE) {
    session_start();
}

if (isset($_SERVER['HTTP_CONTENT_RANGE']) && !empty($_SERVER['HTTP_CONTENT_RANGE']) && isset($_SERVER['CONTENT_LENGTH']) && is_numeric($_SERVER['CONTENT_LENGTH'])) {
    // We get the size of the file uploaded from the client (real, final size)
    $filesize = intval(substr($_SERVER['HTTP_CONTENT_RANGE'], strpos($_SERVER['HTTP_CONTENT_RANGE'], '/') + 1));

    if ($filesize > MAX_FILE_SIZE_UPLOAD) {
        validationFail('bad_request', 'File size allowed must be lower than 10Mb.');
    }

    if (isset($_SESSION['filename']) && is_file($tmpDirectory.'/'.$_SESSION['filename'])) {
        file_put_contents($tmpDirectory.'/'.$_SESSION['filename'], fopen($tmpName, 'r'), FILE_APPEND);
        unlink($tmpName); // We delete the file once we copied it, in order to not use unecessary storage

        // We stop here if the file is not completely loaded
        $currentSize = filesize($tmpDirectory.'/'.$_SESSION['filename']);
        if ($currentSize < $filesize) {
            exit('{"size": '.$currentSize.'}');
        } else {
            $tmpName = $tmpDirectory.'/'.$_SESSION['filename'];
        }
    } else {
        $_SESSION['filename'] = uniqid().'.csv.part';
        move_uploaded_file($tmpName, $tmpDirectory.'/'.$_SESSION['filename']);
        exit('{"size": '.filesize($tmpDirectory.'/'.$_SESSION['filename']).'}');
    }
}

// The file has been completely uploaded.
// You can do whatever you want now
// Like moving the file, which is located in $tmpName

// Note: don't use move_uploaded_file because the chuncked part created a new file and this function will fail.

rename($tmpName, dirname(__FILE__).'/files/'.uniqid().'.csv');
exit('{"success": true}');
edited tags
Link
Cyril N.
  • 235
  • 1
  • 4
  • 16
Source Link
Cyril N.
  • 235
  • 1
  • 4
  • 16
Loading