Hello there
am working now on download script for some projects
I have many option to do from limiting the speed or make the download resumable.
everything works fine except for the resume download, I've tried many script and codes, none of them works :(
here is my last code I used

function download_file($file,$ctype, $is_resume=TRUE)
{
    //First, see if the file exists
    if (!is_file($file))
    {
        die("<b>404 File not found!</b>");
    }

    //Gather relevent info about file
    $size = filesize($file);
    $fileinfo = pathinfo($file);

    //workaround for IE filename bug with multiple periods / multiple dots in filename
    //that adds square brackets to filename - eg. setup.abc.exe becomes setup[1].abc.exe
    $filename = (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')) ?
        preg_replace('/\./', '%2e', $fileinfo['basename'], substr_count($fileinfo['basename'], '.') - 1) :
        $fileinfo['basename'];

    //check if http_range is sent by browser (or download manager)
    if($is_resume && isset($_SERVER['HTTP_RANGE']))
    {
        list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);

        if ($size_unit == 'bytes')
        {
            //multiple ranges could be specified at the same time, but for simplicity only serve the first range
            //http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
            list($range, $extra_ranges) = explode(',', $range_orig, 2);
        }
        else
        {
            $range = '';
        }
    }
    else
    {
        $range = '';
    }

    //figure out download piece from range (if set)
    list($seek_start, $seek_end) = explode('-', $range, 2);

    //set start and end based on range (if set), else set defaults
    //also check for invalid ranges.
    $seek_end = (empty($seek_end)) ? ($size - 1) : min(abs(intval($seek_end)),($size - 1));
    $seek_start = (empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)),0);

    //add headers if resumable
    if ($is_resume)
    {
        //Only send partial content header if downloading a piece of the file (IE workaround)
        if ($seek_start > 0 || $seek_end < ($size - 1))
        {
            header('HTTP/1.1 206 Partial Content');
        }

        header('Accept-Ranges: bytes');
        header('Content-Range: bytes '.$seek_start.'-'.$seek_end.'/'.$size);
    }

    //headers for IE Bugs (is this necessary?)
    //header("Cache-Control: cache, must-revalidate");
    //header("Pragma: public");

    header('Content-Type: ' . $ctype);
    header('Content-Disposition: attachment; filename="' . $filename . '"');
    header('Content-Length: '.($seek_end - $seek_start + 1));

    //open the file
    $fp = fopen($file, 'rb');
    //seek to start of missing part
    fseek($fp, $seek_start);

    //start buffered download
    while(!feof($fp))
    {
        //reset time limit for big files
        set_time_limit(0);
        print(fread($fp, 1024*8));
        flush();
        ob_flush();
    }

    fclose($fp);
    exit;
}

and Implementation

download_file('uploads/somefile.zip','application/octet-stream', TRUE);

Dani AI

Generated

the "Unknown" resume status you see usually means the response to a Range request is not perfectly consistent. Your scripts set 206 and Content-Range, but two common pitfalls will still confuse browsers/download managers: (1) the code keeps streaming past the requested end byte, and (2) suffix ranges (bytes=-N) or out-of-bounds ranges are not handled. Either issue can make resume appear as "unknown" or fail mid-transfer. As hinted, verify the server’s actual response, not just the code.

Quick checks before testing:

  • Turn off compression and buffering for this request: ini_set('zlib.output_compression','0'); and while (ob_get_level()) ob_end_clean();
  • If you use sessions, call session_write_close(); before sending the file.
  • Use curl to validate: curl -I -H "Range: bytes=0-0" https://your/download.php?... should return 206 with Accept-Ranges: bytes and Content-Range: bytes 0-0/FILESIZE. Then curl -s -D - -H "Range: bytes=100-199" ... -o /dev/null should show Content-Length: 100.

Two changes usually fix it:

  • Normalize the range (including suffix) and reject invalid requests with 416.
  • Stop output exactly at the requested length.

Example snippet you can drop into your handler (after you parse $range and open $fp), showing both pieces:

list($start, $end) = explode('-', $range, 2);
if ($start === '' && $end !== '') { // suffix: bytes=-N
    $suffix = (int)$end;
    $start  = max(0, $size - $suffix);
    $end    = $size - 1;
} else {
    $start = max(0, (int)$start);
    $end   = ($end === '' ? $size - 1 : min((int)$end, $size - 1));
}
if ($start > $end || $start >= $size) {
    header('HTTP/1.1 416 Requested Range Not Satisfiable');
    header('Content-Range: bytes */'.$size);
    exit;
}
$length = $end - $start + 1;
fseek($fp, $start);
$chunk = 8192; $sent = 0;
while ($sent < $length && !feof($fp)) {
    $data = fread($fp, min($chunk, $length - $sent));
    if ($data === '' || $data === false) break;
    echo $data; $sent += strlen($data); flush();
}

If you can, offload to the web server (X-Sendfile or X-Accel-Redirect) and let it handle Range logic reliably, while you still set Content-Disposition: attachment.

Recommended Answers

All 6 Replies

everything works fine except for the resume download

What's not working? Did you use ?

Download resume, thats not working, its says unknown
and Yes I used this one and its the same

function download_file($file_path,$file_name,$ctype,$is_attachment){
    @apache_setenv('no-gzip', 1);
    @ini_set('zlib.output_compression', 'Off');

// make sure the file exists
    if (is_file($file_path))
    {
        $file_size  = filesize($file_path);
        $file = @fopen($file_path,"rb");
        if ($file)
        {
            // set the headers, prevent caching
            header("Pragma: public");
            header("Expires: -1");
            header("Cache-Control: public, must-revalidate, post-check=0, pre-check=0");
            header("Content-Disposition: attachment; filename=\"$file_name\"");

            // set appropriate headers for attachment or streamed file
            if ($is_attachment) {
                header("Content-Disposition: attachment; filename=\"$file_name\"");
            }
            else {
                header('Content-Disposition: inline;');
                header('Content-Transfer-Encoding: binary');
            }

            header("Content-Type: " . $ctype);

            //check if http_range is sent by browser (or download manager)
            if(isset($_SERVER['HTTP_RANGE']))
            {
                list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);
                if ($size_unit == 'bytes')
                {
                    //multiple ranges could be specified at the same time, but for simplicity only serve the first range
                    //http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
                    list($range, $extra_ranges) = explode(',', $range_orig, 2);
                }
                else
                {
                    $range = '';
                    header('HTTP/1.1 416 Requested Range Not Satisfiable');
                    exit;
                }
            }
            else
            {
                $range = '';
            }

            //figure out download piece from range (if set)
            ob_clean();
            list($seek_start, $seek_end) = explode('-', $range, 2);

            //set start and end based on range (if set), else set defaults
            //also check for invalid ranges.
            $seek_end   = (empty($seek_end)) ? ($file_size - 1) : min(abs(intval($seek_end)),($file_size - 1));
            $seek_start = (empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)),0);

            //Only send partial content header if downloading a piece of the file (IE workaround)
            if ($seek_start > 0 || $seek_end < ($file_size - 1))
            {
                header('HTTP/1.1 206 Partial Content');
                header('Content-Range: bytes '.$seek_start.'-'.$seek_end.'/'.$file_size);
                header('Content-Length: '.($seek_end - $seek_start + 1));
            }
            else
                header("Content-Length: $file_size");

            header('Accept-Ranges: bytes');

            set_time_limit(0);
            fseek($file, $seek_start);

            while(!feof($file))
            {
                print(@fread($file, 1024*8));
                ob_flush();
                flush();
                if (connection_status()!=0)
                {
                    @fclose($file);
                    exit;
                }
            }

            // file save was a success
            @fclose($file);
            exit;
        }
        else
        {
            // file couldn't be opened
            header("HTTP/1.0 500 Internal Server Error");
            exit;
        }
    }
    else
    {
        // file does not exist
        header("HTTP/1.0 404 Not Found");
        exit;
    }
}

its says unknown

Can you be more specific?

215a4846f7815022ed314d60bcbbdf9e

this what appears to me when downloading

Not sure how that checks if the server has resume enabled.

The server support resume, and the prove if I download the file directly not from "download.php"
its will be resumable,
all these problems appears because I want to force download, because someetime when user want to download video , the vide will be played in browser, so I used the scripts above to do this, and now the resumable proble :(

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.