In uc_file a range transfer starts at the right place, but the rest of the file is sent, not just the requested length. (the loop reads the file up to the end, it breaks of feof()).

The $seek_end should be used to know how many bytes will be sent (or $length = $seek_end - $seek_start + 1).

Thank you.
Alexis Wilke

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

TR’s picture

Priority: Minor » Normal

Yes, I see what you mean. You are correct. Although $seek_end is calculated properly in the code, the loop ALWAYS tries to read until end of file, instead of stopping at $seek_end. Here's the part of the code that needs to be fixed:

  // Start buffered download
  while (!feof($fp)) {

    // Reset time limit for large files
    set_time_limit(0);

    // Push the data to the client.
    print(fread($fp, UC_FILE_BYTE_SIZE));
    flush();
    ob_flush();
  }

Also note that set_time_limit() should be taken out of that loop - we only need to set it once, not keep setting it every 8kbytes. Any patch to this section of code should be checked against #193383: set_time_limit: Centralize calls and prevent warnings and errors, which changes the way set_time_limit() works.

I'm not currently set up to test partial content transfers, so I don't want to roll a patch I can't test. Bumping the priority up to normal.

TR’s picture

Title: Partial download do not properly take the length in account » Partial downloads do not properly take the length into account
torgosPizza’s picture

Wow, good find! I wonder if this issue would help some of our users who are getting incomplete downloads.

Subscribe! (I can test this if you want to post modified code.)

EDIT to add: Seems like we'll need to set a flag in the event of a partial content being downloaded (like a resumed download). If I start a new download and it reads the entire file, then $seek_end is the same as $size (filesize) and the $length ends up being 1, which results in a < 1KB-file being downloaded.

AlexisWilke’s picture

The correct math is like this:

$length = $seek_end - $seek_start + 1

It is used in the header to define the total size of the transfer, and I would suggest you put it in a variable for the header and the loop below.

Thank you.
Alexis

torgosPizza’s picture

That's the math that I used, and I had several downloads fail. Can you post your modified code? Maybe I am doing something wrong. Thanks!

AlexisWilke’s picture

I did not modify my code... I don't even know how to generate a partial download to test...

AlexisWilke’s picture

Hi guys,

According to this document:

http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt

There is another problem in the code which I think is important to mention.

The following will generate E_NOTICEs when $range does not include a dash because $seek_end cannot be set.

list($seek_start, $seek_end) = explode('-', $range, 2);

Then we do this:

$seek_end = intval((empty($seek_end)) ? ($size - 1) : min(abs(intval($seek_end)), ($size - 1)));
$seek_start = intval((empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)), 0));

As you can see, the end is determined only using $seek_end, which is wrong.

If $seek_start is not defined (i.e. "-300") then $seek_end must be set to $size - 1 and $start_end is then set to $size - 300.

At some point, I may write something to run curl() with a range (see CURLOPT_RANGE in curl_setopt()). But well... that code should maybe not say that it supports ranges for now... 8-)

Thank you.
Alexis Wilke

longwave’s picture

Component: Code » File downloads

Changing component.

TR’s picture

Version: 6.x-2.x-dev » 8.x-4.x-dev
Issue summary: View changes
joelstein’s picture

I am encountering an "Undefined offset: 1" error on line 349 of uc_file.pages.inc, in _uc_file_download_transfer(), when a user is downloading a purchased file.

The offending line:

list($range, $extra_ranges) = explode(',', substr($_SERVER['HTTP_RANGE'], 6), 2);

My error logger noted the "Range" header in this request was bytes=4915200-. Note that there are no commas in that range header (which is a valid request).

Since the $extra ranges variable is not used elsewhere in the code, I propose we simplify this line to the following which will gracefully handle situations where there is only a single range:

list($range) = explode(',', substr($_SERVER['HTTP_RANGE'], 6), 1);

The attached patch accomplishes this for D7.

Status: Needs review » Needs work
TR’s picture

That doesn't fix #1 and #7 ... and we still need a test to demonstrate that any fix does the right thing ...