Ticket #8055: mythweb-asxstreamingfix.diff

File mythweb-asxstreamingfix.diff, 7.7 KB (added by Dagmar d'Surreal, 14 years ago)

Makes the ASX streaming work again by adding more-or-less full support for byterange requests

  • mythplugins/mythweb/modules/stream/stream_flv.pl

     
    4949    $sh->execute($chanid,$starttime);
    5050    $x = $sh->fetchrow_array;
    5151    $y = $sh->fetchrow_array if ($x);
    52     $width = round_even(width);
     52    $width = round_even($width);
    5353    if ($x && $y) {
    5454        $height = round_even($width * ($y/$x));
    5555    } else {
  • mythplugins/mythweb/modules/stream/stream_raw.pl

     
    1111    $| = 1;
    1212
    1313    use HTTP::Date;
     14    use Fcntl ':seek'; # platform-portable constants for sysseek()
    1415
    1516# File size
    1617    my $size = -s $filename;
     
    6869    my $read_size  = 1024;
    6970    my $mtime      = (stat($filename))[9];
    7071
     72    # deliver_chunk takes as arguments the starting and ending offsets of the chunk of
     73    # data we want to be retrieved from the DATA filehandle.  Presently, this does no
     74    # error checking, so if the disk read fails you're on your own.
     75    sub deliver_chunk {
     76        my ($start, $end) = @_;
     77        my $chunk_size = $end - $start + 1;
     78        my $buf_size = ($read_size > $chunk_size) ? $chunk_size : $read_size;
     79        my $buffer;
     80       
     81        sysseek DATA, $start, SEEK_SET;
     82        while (sysread DATA, $buffer, $buf_size) {
     83            print $buffer;
     84            $chunk_size -= $buf_size;
     85            last if ($chunk_size == 0);
     86            if ($chunk_size < $buf_size) {
     87                $buf_size = $chunk_size;
     88            }
     89        }
     90    }
     91
    7192# Handle cache hits/misses
    7293    if ( $ENV{'HTTP_IF_MODIFIED_SINCE'}) {
    7394        my $check_time = str2time($ENV{'HTTP_IF_MODIFIED_SINCE'});
     
    80101    }
    81102
    82103# Requested a range?
     104    # See also http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
    83105    if ($ENV{'HTTP_RANGE'}) {
    84     # Figure out the size of the requested chunk
    85         ($start, $end) = $ENV{'HTTP_RANGE'} =~ /bytes\W+(\d*)-(\d*)\W*$/;
    86         if ($end < 1 || $end > $size) {
    87             $end = $size;
    88         }
    89         $size = $end - $start+1;
    90         if ($read_size > $size) {
    91             $read_size = $size;
    92         }
    93         print header(-status                => "206 Partial Content",
    94                      -type                  => $type,
    95                      -Content_length        => $size,
    96                      -Accept_Ranges         => 'bytes',
    97                      -Content_Range         => "bytes $start-$end/$total_size",
    98                      -Last_Modified         => time2str($mtime),
    99                      -Content_disposition => " attachment; filename=\"$name.$suffix\""
    100                  );
    101     }
    102     else {
     106        my $http_range = $ENV{'HTTP_RANGE'};
     107        $http_range =~ s/\s*//g; # Draft doesn't /forbid/ spaces.
     108        # Anything but a bytes request is either malicious or very confused.
     109        unless ($http_range =~ s/^bytes=//i) {
     110            print header(-status => "400 Bad Request (malformed range retrieval)"
     111                        );
     112            exit;
     113        }
     114        # By this point, $http_range should mostly contain comma-separated ranges.
     115
     116        # The draft spec doesn't specify an upper limit to the number of chunks, but
     117        # we darn well will--especially since nothing currently asks for more than one.
     118        my @chunks = split(/,/, $http_range, 101);
     119        unless (scalar(@chunks)) {
     120            print header(-status => "400 Bad Request (missing range retrieval)"
     121                        );
     122            exit;
     123        } elsif (scalar(@chunks) == 101) {
     124            print header(-status => "400 Bad Request (ridiculous range retrieval)"
     125                        );
     126            exit;
     127        }
     128
     129        # This is part sanity check, part precomputation of values which will be stacked
     130        # into a nice array of array refs.
     131        my @refs;
     132        foreach (@chunks) {
     133            unless (/(\d+-|-\d+|\d+-\d+)/) {
     134                print header(-status => "400 Bad Request (incoherent range retrieval)"
     135                            );
     136                exit;
     137            }
     138            if ((defined $start) && (defined $end) && ($start > $end)) {
     139                print header(-status => "400 Bad Request (absurd range retrieval)"
     140                            );
     141                exit;
     142            }
     143
     144            # Technically this counts as de-tainting, if a bit overkill
     145            my ($start, $end) = shift(@chunks) =~ /(\d*)-(\d*)/;
     146            # Now to determine what type of range request we're looking at.
     147            if (defined $start) {
     148                # So, at least the starting offset is straightforward.
     149                unless ($end) {
     150                    # From offset to the end of the file, wherever that is.
     151                    push(@refs, [$start, $size - 1]);
     152                } else {
     153                    # A decent, wholesome, upstanding range request.
     154                    push(@refs, [$start, $end]);
     155                }
     156            } else {
     157                # No start value means this is asking for the tail end of the file.
     158                if ($end > $size - 1) {
     159                    # This request is larger than the file, but still salvageable.
     160                    push(@refs, [0, $size - 1]);
     161                } else {
     162                    # This is a normal request for the last $end bytes of the file.
     163                    push(@refs, [$size - 1 - $end, $size - 1]);
     164                }
     165            }
     166        }
     167
     168        if (scalar(@refs) == 1) {
     169            # Single-part range request
     170            my ($start, $end) = @{$refs[0]};
     171            my $chunk_size = $end - $start + 1;
     172            print header(-status                => "206 Partial Content",
     173                         -type                  => $type,
     174                         -Content_length        => $chunk_size,
     175                         -Accept_Ranges         => 'bytes',
     176                         -Content_Range         => "bytes $start-$end/$total_size",
     177                         -Last_Modified         => time2str($mtime),
     178                         -Content_disposition => " attachment; filename=\"$name.$suffix\""
     179                        );
     180            deliver_chunk($start, $end);
     181        } else {
     182            # Multi-part range request, which will probably never be used but hey...
     183            # since we're already here, might as well take a whack at it.
     184            # As weakly documented as this is defined, it may or may not work.
     185            print header(-status                => "206 Partial Content",
     186                         -type                  => "multipart/x-byteranges",
     187                         -Last_Modified         => time2str($mtime),
     188                         -Accept_Ranges         => 'bytes',   # Questionable utility
     189                        );
     190            print multipart_init;
     191            my $laziness;
     192            foreach $ref (@refs) {
     193                if ($laziness) {
     194                    print multipart_end;
     195                } else {
     196                    $laziness=1;
     197                }
     198                my ($start, $end) = @{$ref};
     199                my $chunk_size = $end - $start + 1;
     200                print multipart_start(-type             => $type,
     201                                      -Content_Range    => "bytes $start-$end/$total_size"
     202                                     );
     203                deliver_chunk($start, $end);
     204            }
     205            print multipart_final;
     206        }
     207
     208    } else {
    103209        print header(-type                  => $type,
    104210                    -Content_length         => $size,
    105211                    -Accept_Ranges          => 'bytes',
    106212                    -Last_Modified          => time2str($mtime),
    107213                    -Content_disposition => " attachment; filename=\"$name.$suffix\""
    108214                 );
     215        deliver_chunk($start, $end);
    109216    }
    110 
    111 # Seek to the requested position
    112     sysseek DATA, $start, 0;
    113 
    114 # Print the content to the browser
    115     my $buffer;
    116     while (sysread DATA, $buffer, $read_size ) {
    117         print $buffer;
    118         $size -= $read_size;
    119         if ($size == 0) {
    120             last;
    121         }
    122         if ($size < $read_size) {
    123             $read_size = $size;
    124         }
    125     }
    126     close DATA;
    127 
    128217    1;