App-MHFS

 view release on metacpan or  search on metacpan

lib/MHFS/HTTP/Server/Client/Request.pm  view on Meta::CPAN

use Symbol 'gensym';
use Devel::Peek;
use Encode qw(decode encode);
use constant {
    MAX_REQUEST_SIZE => 8192,
};
use FindBin;
use File::Spec;
use MHFS::EventLoop::Poll;
use MHFS::Process;
use MHFS::Util qw(get_printable_utf8 LOCK_GET_LOCKDATA getMIME shell_escape escape_html_noquote parse_ipv4);
BEGIN {
    if( ! (eval "use JSON; 1")) {
        eval "use JSON::PP; 1" or die "No implementation of JSON available";
        warn __PACKAGE__.": Using PurePerl version of JSON (JSON::PP)";
    }
}

# Optional dependency, Alien::Tar::Size
BEGIN {
    use constant HAS_Alien_Tar_Size => (eval "use Alien::Tar::Size; 1");

lib/MHFS/HTTP/Server/Client/Request.pm  view on Meta::CPAN

            say "RECV: $rl";
            if(($self->{'method'} ne 'GET') && ($self->{'method'} ne 'HEAD') && ($self->{'method'} ne 'PUT')) {
                say "X-MHFS-CONN-ID: " . $self->{'outheaders'}{'X-MHFS-CONN-ID'} . 'Invalid method: ' . $self->{'method'}. ', closing conn';
                return undef;
            }
            my ($path, $querystring) = ($self->{'uri'} =~ /^([^\?]+)(?:\?)?(.*)$/g);
            say("raw path: $path\nraw querystring: $querystring");

            # transformations
            ## Path
            $path = uri_unescape($path);
            my %pathStruct = ( 'unescapepath' => $path );

            # collapse slashes
            $path =~ s/\/{2,}/\//g;
            say "collapsed: $path";
            $pathStruct{'unsafecollapse'} = $path;

            # without trailing slash
            if(index($pathStruct{'unsafecollapse'}, '/', length($pathStruct{'unsafecollapse'})-1) != -1) {
                chop($path);
                say "no slash path: $path ";

lib/MHFS/HTTP/Server/Client/Request.pm  view on Meta::CPAN


            ## Querystring
            my %qsStruct;
            # In the querystring spaces are sometimes encoded as + for legacy reasons unfortunately
            $querystring =~ s/\+/%20/g;
            my @qsPairs = split('&', $querystring);
            foreach my $pair (@qsPairs) {
                my($key, $value) = split('=', $pair);
                if(defined $value) {
                    if(!defined $qsStruct{$key}) {
                        $qsStruct{$key} = uri_unescape($value);
                    }
                    else {
                        if(ref($qsStruct{$key}) ne 'ARRAY') {
                            $qsStruct{$key} = [$qsStruct{$key}];
                        };
                        push @{$qsStruct{$key}}, uri_unescape($value);
                    }
                }
            }

            $self->{'path'} = \%pathStruct;
            $self->{'qs'} = \%qsStruct;
            $self->{'on_read_ready'} = \&want_headers;
            #return want_headers($self);
            goto &want_headers;
        }

lib/MHFS/HTTP/Server/Client/Request.pm  view on Meta::CPAN

    my $disposition = 'inline';
    if($opt->{'attachment'}) {
        $disposition = 'attachment';
        $filename = $opt->{'attachment'};
    }
    elsif($opt->{'inline'}) {
        $filename = $opt->{'inline'};
    }
    if($filename) {
        my $sendablebytes = encode('UTF-8', get_printable_utf8($filename));
        $headtext .=   "Content-Disposition: $disposition; filename*=UTF-8''".uri_escape($sendablebytes)."; filename=\"$sendablebytes\"\r\n";
    }

    $self->{'outheaders'}{'Accept-Ranges'} //= 'bytes';
    $self->{'outheaders'}{'Connection'} //= $self->{'header'}{'Connection'};
    $self->{'outheaders'}{'Connection'} //= 'keep-alive';

    # SharedArrayBuffer
    if($opt->{'allowSAB'}) {
        say "sending SAB headers";
        $self->{'outheaders'}{'Cross-Origin-Opener-Policy'} =  'same-origin';

lib/MHFS/HTTP/Server/Client/Request.pm  view on Meta::CPAN

# encodes path and querystring
# path and query string keys and values must be bytes not unicode string
sub SendRedirect {
    my ($self, $code, $path, $qs) = @_;
    my $url;
    # encode the path component
    while(length($path)) {
        my $slash = index($path, '/');
        my $len = ($slash != -1) ? $slash : length($path);
        my $pathcomponent = substr($path, 0, $len, '');
        $url .= uri_escape($pathcomponent);
        if($slash != -1) {
            substr($path, 0, 1, '');
            $url .= '/';
        }
    }
    # encode the querystring
    if($qs) {
        $url .= '?';
        foreach my $key (keys %{$qs}) {
            my @values;
            if(ref($qs->{$key}) ne 'ARRAY') {
                push @values, $qs->{$key};
            }
            else {
                @values = @{$qs->{$key}};
            }
            foreach my $value (@values) {
                $url .= uri_escape($key).'='.uri_escape($value) . '&';
            }
        }
        chop $url;
    }

    @_ = ($self, $code, $url);
    goto &SendRedirectRawURL;
}

sub SendLocalFile {

lib/MHFS/HTTP/Server/Client/Request.pm  view on Meta::CPAN

        return min($filelength, $tocheck);
    };

    $self->_SendDataItem(\%fileitem, {
        'size'     => $filelength,
        'mime'     => $mime,
        'filename' => $filename
    });
}

# to do get rid of shell escape, launch ssh without blocking
sub SendFromSSH {
    my ($self, $sshsource, $filename, $node) = @_;
    my @sshcmd = ('ssh', $sshsource->{'userhost'}, '-p', $sshsource->{'port'});
    my $fullescapedname = "'" . shell_escape($filename) . "'";
    my $folder = $sshsource->{'folder'};
    my $size = $node->[1];
    my @cmd;
    if(defined $self->{'header'}{'_RangeStart'}) {
        my $start = $self->{'header'}{'_RangeStart'};
        my $end = $self->{'header'}{'_RangeEnd'} // ($size - 1);
        my $bytestoskip =  $start;
        my $count = $end - $start + 1;
        @cmd = (@sshcmd, 'dd', 'skip='.$bytestoskip, 'count='.$count, 'bs=1', 'if='.$fullescapedname);
    }
    else{
        @cmd = (@sshcmd, 'cat', $fullescapedname);
    }
    say "SendFromSSH (BLOCKING)";
    open(my $cmdh, '-|', @cmd) or die("SendFromSSH $!");

    $self->SendPipe($cmdh, basename($filename), $size);
    return 1;
}

# ENOTIMPLEMENTED
sub Proxy {

lib/MHFS/HTTP/Server/Client/Request.pm  view on Meta::CPAN

        if(index($request->{'path'}{'unsafecollapse'}, '/', length($request->{'path'}{'unsafecollapse'})-1) == -1) {
            $request->SendFile($requestfile);
        }
        else {
            $request->Send404;
        }
    }
    # is directory
    elsif (-d _) {
        # ends with slash
        if(index($request->{'path'}{'unescapepath'}, '/', length($request->{'path'}{'unescapepath'})-1) != -1) {
            my $index = $requestfile.'/index.html';
            if(-f $index) {
                $request->SendFile($index);
                return;
            }
            $request->Send404;
        }
        else {
            # redirect to slash path
            my $bn = basename($requestfile);

lib/MHFS/HTTP/Server/Client/Request.pm  view on Meta::CPAN

        if(index($self->{'path'}{'unsafecollapse'}, '/', length($self->{'path'}{'unsafecollapse'})-1) == -1) {
            $self->SendFile($requestfile);
        }
        else {
            $self->Send404;
        }
        return;
    }
    elsif(-d _) {
        # ends with slash
        if((substr $self->{'path'}{'unescapepath'}, -1) eq '/') {
            opendir ( my $dh, $requestfile ) or die "Error in opening dir $requestfile\n";
            my $buf;
            my $filename;
            while( ($filename = readdir($dh))) {
                next if(($filename eq '.') || ($filename eq '..'));
                next if(!(-s "$requestfile/$filename"));
                my $url = uri_escape($filename);
                $url .= '/' if(-d _);
                $buf .= '<a href="' . $url .'">'.${escape_html_noquote(decode('UTF-8', $filename, Encode::LEAVE_SRC))} .'</a><br><br>';
            }
            closedir($dh);
            $self->SendHTML($buf);
            return;
        }
        # redirect to slash path
        else {
            $self->SendRedirect(301, basename($requestfile).'/');
            return;
        }

lib/MHFS/Kodi/Util.pm  view on Meta::CPAN

package MHFS::Kodi::Util v0.7.0;
use 5.014;
use strict; use warnings;
use Exporter 'import';
use MHFS::Util qw(uri_escape_path_utf8 escape_html_noquote);
our @EXPORT_OK = qw(html_list_item);

sub html_list_item {
    my ($item, $isdir, $label) = @_;
    $label //= $item;
    my $url = uri_escape_path_utf8($item);
    $url .= '/?fmt=html' if($isdir);
    '<li><a href="' . $url .'">'. ${escape_html_noquote($label)} .'</a></li>'
}
1;

lib/MHFS/Plugin/BitTorrent/Client/Interface.pm  view on Meta::CPAN

package MHFS::Plugin::BitTorrent::Client::Interface v0.7.0;
use 5.014;
use strict; use warnings;
use feature 'say';
use MHFS::BitTorrent::Client;
use MHFS::Util qw(escape_html do_multiples get_SI_size);
use URI::Escape qw(uri_escape);

sub is_video {
    my ($name) = @_;
    my ($ext) = $name =~ /\.(mkv|avi|mp4|webm|flv|ts|mpeg|mpg|m2t|m2ts|wmv)$/i;
    return $ext;
}

sub is_mhfs_music_playable {
    my ($name) = @_;
    return $name =~ /\.(?:flac|mp3|wav)$/i;

lib/MHFS/Plugin/BitTorrent/Client/Interface.pm  view on Meta::CPAN

        'bytes_done' => sub { MHFS::BitTorrent::Client::torrent_d_bytes_done($server, $hash, @_); },
        'size_bytes' => sub { MHFS::BitTorrent::Client::torrent_d_size_bytes($server, $hash, @_); },
        'name'       => sub { MHFS::BitTorrent::Client::torrent_d_name($server, $hash, @_); },
        }, sub {
        if( ! defined $_[0]) { $request->Send404; return;}
        my ($data) = @_;
        my $torrent_raw = $data->{'name'};
        my $bytes_done  = $data->{'bytes_done'};
        my $size_bytes  = $data->{'size_bytes'};
        # print out the current torrent status
        my $torrent_name = ${escape_html($torrent_raw)};
        my $size_print = get_SI_size($size_bytes);
        my $done_print = get_SI_size($bytes_done);
        my $percent_print = (sprintf "%u%%", ($bytes_done/$size_bytes)*100);
        my $buf = '<h1>Torrent</h1>';
        $buf  .=  '<h3><a href="../video">Video</a> | <a href="../music">Music</a></h3>';
        $buf   .= '<table border="1" >';
        $buf   .= '<thead><tr><th>Name</th><th>Size</th><th>Done</th><th>Downloaded</th></tr></thead>';
        $buf   .= "<tbody><tr><td>$torrent_name</td><td>$size_print</td><td>$percent_print</td><td>$done_print</td></tr></tbody>";
        $buf   .= '</table>';

lib/MHFS/Plugin/BitTorrent/Client/Interface.pm  view on Meta::CPAN

            # print out the files with usage options
            MHFS::BitTorrent::Client::torrent_file_information($server, $qs->{'infohash'}, $torrent_raw, sub {
            if(! defined $_[0]){ $request->Send404; return; };
            my ($tfi) = @_;
            my @files = sort (keys %$tfi);
            $buf .= '<br>';
            $buf .= '<table border="1" >';
            $buf .= '<thead><tr><th>File</th><th>Size</th><th>DL</th><th>Play in browser</th></tr></thead>';
            $buf .= '<tbody';
            foreach my $file (@files) {
                my $htmlfile = ${escape_html($file)};
                my $urlfile = uri_escape($file);
                my $link = '<a href="get_video?name=' . $urlfile . '&fmt=noconv">DL</a>';
                my $playlink = play_in_browser_link($file, $urlfile);
                $buf .= "<tr><td>$htmlfile</td><td>" . get_SI_size($tfi->{$file}{'size'}) . "</td><td>$link</td>";
                $buf .= "<td>$playlink</td>" if(!defined($qs->{'playinbrowser'}) || ($qs->{'playinbrowser'} == 1));
                $buf .= "</tr>";
            }
            $buf .= '</tbody';
            $buf .= '</table>';

            $request->SendHTML($buf);

lib/MHFS/Plugin/BitTorrent/Client/Interface.pm  view on Meta::CPAN

            while(1) {
                if($curtor =~ /^\[(u?)['"](.+)['"],\s'(.+)',\s([0-9]+),\s([0-9]+),\s([0-9]+)\]$/) {
                    my %torrent;
                    my $is_unicode = $1;
                    $torrent{'name'} = $2;
                    $torrent{'hash'} = $3;
                    $torrent{'size_bytes'} = $4;
                    $torrent{'bytes_done'} = $5;
                    $torrent{'private'} = $6;
                    if($is_unicode) {
                        my $escaped_unicode = $torrent{'name'};
                        $torrent{'name'} =~ s/\\u(.{4})/chr(hex($1))/eg;
                        $torrent{'name'} =~ s/\\x(.{2})/chr(hex($1))/eg;
                        my $decoded_as = $torrent{'name'};
                        $torrent{'name'} = ${escape_html($torrent{'name'})};
                        if($qs->{'logunicode'}) {
                            say 'unicode escaped: ' . $escaped_unicode;
                            say 'decoded as: ' . $decoded_as;
                            say 'html escaped ' . $torrent{'name'};
                        }
                    }
                    $buf .= '<tr><td>' . $torrent{'name'} . '</td><td>' . $torrent{'hash'} . '</td><td>' . $torrent{'size_bytes'} . '</td><td>' . $torrent{'bytes_done'} . '</td><td>' . $torrent{'private'} . '</td></tr>';
                    $curtor = '';
                }
                else {
                    my $line = shift @lines;
                    if(! $line) {
                        last;
                    }

lib/MHFS/Plugin/GetVideo.pm  view on Meta::CPAN

package MHFS::Plugin::GetVideo v0.7.0;
use 5.014;
use strict; use warnings;
use feature 'say';
use Data::Dumper qw (Dumper);
use Fcntl qw(:seek);
use Feature::Compat::Try;
use Scalar::Util qw(weaken);
use URI::Escape qw (uri_escape);
use Devel::Peek qw(Dump);
no warnings "portable";
use Config;
use MHFS::Process;
use MHFS::Util qw(space2us LOCK_WRITE round shellcmd_unlock ASYNC pid_running read_text_file write_text_file ceil_div);

sub new {
    my ($class, $settings) = @_;

    if($Config{ivsize} < 8) {

lib/MHFS/Plugin/GetVideo.pm  view on Meta::CPAN

        catch ($e) {
            say "$requestfile does not exist or is not UTF-8";
            ''
        }
    };
    my $subm3u;
    my $newm3ucontent = '';
    foreach my $line (split("\n", $m3ucontent)) {
        # master playlist doesn't get written with base url ...
        if($line =~ /^(.+)\.m3u8_v$/) {
            $subm3u = "get_video?".$settings->{VIDEO_TMPDIR_QS}."&fmt=noconv&name=" . uri_escape("$1/$1");
            $line = $subm3u . '.m3u8_v';
        }
        $newm3ucontent .= $line . "\n";
    }

    # Always start at 0, even if we encoded half of the movie
    #$newm3ucontent .= '#EXT-X-START:TIME-OFFSET=0,PRECISE=YES' . "\n";

    # if ffmpeg created a sub include it in the playlist
    ($requestfile =~ /^(.+)\.m3u8$/);

lib/MHFS/Plugin/Kodi.pm  view on Meta::CPAN

package MHFS::Plugin::Kodi v0.7.0;
use 5.014;
use strict; use warnings;
use feature 'say';
use File::Basename qw(basename);
use Cwd qw(abs_path getcwd);
use URI::Escape qw(uri_escape);
use Encode qw(decode encode_utf8);
use File::Path qw(make_path);
use Data::Dumper qw(Dumper);
use Scalar::Util qw(weaken);
use MIME::Base64 qw(encode_base64url decode_base64url);
use Devel::Peek qw(Dump);
use MHFS::Kodi::TVShows;
use MHFS::Kodi::Movie;
use MHFS::Kodi::MovieEdition;
use MHFS::Kodi::MovieEditions;
use MHFS::Kodi::MoviePart;
use MHFS::Kodi::Movies;
use MHFS::Kodi::MovieSubtitle;
use MHFS::Kodi::Season;
use MHFS::Process;
use MHFS::Promise;
use MHFS::Util qw(base64url_to_str str_to_base64url uri_escape_path_utf8 read_text_file_lossy write_text_file_lossy decode_utf_8 escape_html_noquote fold_case write_file read_file);
use Feature::Compat::Try;
BEGIN {
    if( ! (eval "use JSON; 1")) {
        eval "use JSON::PP; 1" or die "No implementation of JSON available";
        warn __PACKAGE__.": Using PurePerl version of JSON (JSON::PP)";
    }
}

sub readtvdir {
    my ($self, $tvshows, $source, $b_tvdir) = @_;

lib/MHFS/Plugin/Kodi.pm  view on Meta::CPAN

            $request->Send400;
            return;
        };
        try {
            $tvitem = $self->_get_tv_item($tvshows, $showid, $season, $source, $b64_item);
        } catch($e) {
            say "exception $e";
            $request->Send404;
            return;
        }
        if (substr($request->{'path'}{'unescapepath'}, -1) ne '/') {
            # redirect if we aren't accessing a file
            if (!exists $tvitem->{b_path}) {
                $request->SendRedirect(301, substr($request->{'path'}{'unescapepath'}, rindex($request->{'path'}{'unescapepath'}, '/')+1).'/');
            } else {
                $request->SendFile($tvitem->{b_path});
            }
            return;
        }
    } else {
        $tvitem = bless {tvshows => $tvshows}, 'MHFS::Kodi::TVShows';
    }
    if(exists $request->{qs}{fmt} && $request->{qs}{fmt} eq 'html') {
        my $buf = $tvitem->TO_HTML;

lib/MHFS/Plugin/Kodi.pm  view on Meta::CPAN

                            say "subfile $subfile";
                        }
                    }
                }
            }
            $movieitem = $self->_search_movie_library($movies, $movieid, $source, $editionname, $partname, $subfile);
        } catch ($e) {
            $request->Send404;
            return;
        }
        if (substr($request->{'path'}{'unescapepath'}, -1) ne '/') {
            # redirect if we aren't accessing a file
            if (!exists $movieitem->{b_path}) {
                $request->SendRedirect(301, substr($request->{'path'}{'unescapepath'}, rindex($request->{'path'}{'unescapepath'}, '/')+1).'/');
            } else {
                $request->SendFile($movieitem->{b_path});
            }
            return;
        }
    } else {
        $movieitem = bless {movies => $movies}, 'MHFS::Kodi::Movies';
    }
    # render
    if(exists $request->{qs}{fmt} && $request->{qs}{fmt} eq 'html') {

lib/MHFS/Plugin/Kodi.pm  view on Meta::CPAN

<li>Click <b>Configure</b> (or open the MHFS Video settings) and fill in <b>$baseurl</b> (the URL of the MHFS server you want to connect to).</li>
<li>MHFS Video should now be installed, you should be able to access it from <b>Add-ons->Video add-ons->MHFS Video</b> on the main menu</li>
</ol>
<ul>
<a href="$repo_addon_name">$repo_addon_name</a>
</ul>
END_HTML
        $request->SendHTML($html);
        return;
    } elsif (substr($request_path, length($kodidir)+1) ne $repo_addon_name ||
                substr($request->{'path'}{'unescapepath'}, -1) eq '/') {
        $request->Send404;
        return;
    }
    my $xml = <<"END_XML";
<?xml version="1.0" encoding="UTF-8"?>
<addon id="repository.mhfs"
    name="MHFS Repository"
    version="$repo_addon_name"
    provider-name="G4Vi">
<extension point="xbmc.addon.repository" name="MHFS Repository">

lib/MHFS/Plugin/Kodi.pm  view on Meta::CPAN

    if($qs){
        foreach my $key (keys %{$qs}) {
            my @values;
            if(ref($qs->{$key}) ne 'ARRAY') {
                push @values, $qs->{$key};
            }
            else {
                @values = @{$qs->{$key}};
            }
            foreach my $value (@values) {
                $url .= uri_escape($key).'='.uri_escape($value) . '&';
            }
        }
    }
    chop $url;
    return _curl($server, [encode_utf8($url)], sub {
        $cb->(decode_json($_[0]));
    });
}

sub _TMDB_api_promise {

lib/MHFS/Plugin/MusicLibrary.pm  view on Meta::CPAN

use feature 'say';
use Cwd qw(abs_path getcwd);
use File::Find;
use Data::Dumper;
use Devel::Peek;
use Fcntl ':mode';
use File::stat;
use File::Basename;
use File::Path qw(make_path);
use Scalar::Util qw(looks_like_number);
use MHFS::Util qw(get_printable_utf8 escape_html_noquote LOCK_GET_LOCKDATA LOCK_WRITE UNLOCK_WRITE);
BEGIN {
    if( ! (eval "use JSON; 1")) {
        eval "use JSON::PP; 1" or die "No implementation of JSON available";
        warn __PACKAGE__.": Using PurePerl version of JSON (JSON::PP)";
    }
}
use Encode qw(decode encode);
use URI::Escape;
use Storable qw(dclone);
use Fcntl ':mode';

lib/MHFS/Plugin/MusicLibrary.pm  view on Meta::CPAN

        return undef if( $size eq 0);
        return [$basepath, $size, \@tree, $utf8name];
    }
}

sub ToHTML {
    my ($files, $where) = @_;
    $where //= '';
    my $buf = '';
    my $name_unencoded = $files->[3];
    my $name = ${escape_html_noquote($name_unencoded)};
    if($files->[2]) {
        my $dir = $files->[0];
        $buf .= '<tr>';
        $buf .= '<td>';
        $buf .= '<table border="1" class="tbl_track">';
        $buf .= '<tbody>';
        $buf .= '<tr class="track">';
        $buf .= '<th>' . $name . '</th>';
        $buf .= '<th><a href="#">Play</a></th><th><a href="#">Queue</a></th><th><a href="music_dl?action=dl&name=' . uri_escape_utf8($where.$name_unencoded) . '">DL</a></th>';
        $buf .= '</tr>';
        $where .= $name_unencoded . '/';
        foreach my $file (@{$files->[2]}) {
            $buf .= ToHTML($file, $where) ;
        }
        $buf .= '</tbody></table>';
        $buf .= '</td>';

    }
    else {
        if($where eq '') {
                $buf .= '<table border="1" class="tbl_track">';
                $buf .= '<tbody>';
        }
        $buf .= '<tr class="track">';
        $buf .= '<td>' . $name . '</td>';
        $buf .= '<td><a href="#">Play</a></td><td><a href="#">Queue</a></td><td><a href="music_dl?action=dl&name=' . uri_escape_utf8($where.$name_unencoded).'">DL</a></td>';
        if($where eq '') {
                $buf .= '</tr>';
                $buf .= '</tbody></table>';
                return $buf;
        }
    }
    $buf .= '</tr>';
    return $buf;
}

lib/MHFS/Plugin/Playlist.pm  view on Meta::CPAN

package MHFS::Plugin::Playlist v0.7.0;
use 5.014;
use strict; use warnings;
use feature 'say';
use Data::Dumper;
use URI::Escape qw(uri_escape);
use Encode qw(decode);

sub video_get_m3u8 {
    my ($video, $urlstart) = @_;
    my $buf;
    my $m3u8 = <<'M3U8END';
#EXTM3U
#EXTVLCOPT:network-caching=40000'
M3U8END

lib/MHFS/Plugin/Playlist.pm  view on Meta::CPAN

            'root' => $video->{'src_file'}{'root'},
            'on_file' => sub {
                my ($path, $shortpath) = @_;
                push @files, $shortpath;
            }
        });
    }

    foreach my $file (@files) {
        $m3u8 .= '#EXTINF:0, ' . decode('UTF-8', $file, Encode::LEAVE_SRC) . "\n";
        $m3u8 .= $urlstart . uri_escape($file) . "\n";
        #$m3u8 .= $urlstart . small_url_encode($file) . "\n";
    }
    return \$m3u8;
}

sub new {
    my ($class, $settings, $server) = @_;
    my $self =  {};
    bless $self, $class;

lib/MHFS/Plugin/VideoLibrary.pm  view on Meta::CPAN

package MHFS::Plugin::VideoLibrary v0.7.0;
use 5.014;
use strict; use warnings;
use feature 'say';
use Feature::Compat::Try;
use Encode qw(decode);
use URI::Escape qw (uri_escape);
use MHFS::Util qw(output_dir_versatile escape_html uri_escape_path);

sub player_video {
    my ($request) = @_;
    my $qs = $request->{'qs'};
    my $server = $request->{'client'}{'server'};
    my $packagename = __PACKAGE__;
    my $settings = $server->{'settings'};
    my $self = $request->{'client'}{'server'}{'loaded_plugins'}{$packagename};

    my $buf =  "<html>";

lib/MHFS/Plugin/VideoLibrary.pm  view on Meta::CPAN


    my $urlconstant = 'lib='.$lib.'&sid='.$sid;
    my $playlisturl = "playlist/video/$sid/";

    my $buf;
    output_dir_versatile($dir, {
        'root' => $dir,
        'min_file_size' => 100000,
        'on_dir_start' => sub {
            my ($realpath, $unsafe_relpath) = @_;
            my $relpath = uri_escape($unsafe_relpath);
            my $disppath = escape_html(decode('UTF-8', $unsafe_relpath));
            $buf .= '<li><div class="row">';
            $buf .= '<a href="#' . $relpath . '_hide" class="hide" id="' . $$disppath . '_hide">' . "$$disppath</a>";
            $buf .= '<a href="#' . $relpath . '_show" class="show" id="' . $$disppath . '_show">' . "$$disppath</a>";
            $buf .= '    <a href="'.$playlisturl . uri_escape_path($unsafe_relpath) . '?fmt=m3u8">M3U</a>';
            $buf .= '<div class="list"><ul>';
        },
        'on_dir_end' => sub {
            $buf .= '</ul></div></div></li>';
        },
        'on_file' => sub {
            my ($realpath, $unsafe_relpath, $unsafe_name) = @_;
            my $relpath = uri_escape($unsafe_relpath);
            my $filename = escape_html(decode('UTF-8', $unsafe_name));
            $buf .= '<li><a href="video?'.$urlconstant.'&name='.$relpath.'&fmt=' . $fmt . '" class="mediafile">' . $$filename . '</a>    <a href="get_video?'.$urlconstant.'&name=' . $relpath . '&fmt=' . $fmt . '">DL</a>    <a href="'.$playlisturl . u...
        }
    });
    return \$buf;
}

sub new {
    my ($class, $settings) = @_;
    my $self =  {};
    bless $self, $class;

lib/MHFS/Plugin/Youtube.pm  view on Meta::CPAN

use 5.014;
use strict; use warnings;
use feature 'say';
use Data::Dumper;
use feature 'state';
use Encode;
use URI::Escape;
use Scalar::Util qw(looks_like_number weaken);
use File::stat;
use MHFS::Process;
use MHFS::Util qw(escape_html LOCK_WRITE UNLOCK_WRITE);
BEGIN {
    if( ! (eval "use JSON; 1")) {
        eval "use JSON::PP; 1" or die "No implementation of JSON available";
        warn __PACKAGE__.": Using PurePerl version of JSON (JSON::PP)";
    }
}
sub searchbox {
    my ($self, $request) = @_;
    #my $html = '<form  name="searchbox" action="' . $request->{'path'}{'basename'} . '">';
    my $html = '<form  name="searchbox" action="yt">';
    $html .= '<input type="text" width="50%" name="q" ';
    my $query = $request->{'qs'}{'q'};
    if($query) {
        $query =~ s/\+/ /g;
        my $escaped = escape_html($query);
        $html .= 'value="' . $$escaped . '"';
    }
    $html .=  '>';
    if($request->{'qs'}{'media'}) {
        $html .= '<input type="hidden" name="media" value="' . $request->{'qs'}{'media'} . '">';
    }
    $html .= '<input type="submit" value="Search">';
    $html .= '</form>';
    return $html;
}
sub ytplayer {
    my ($self, $request) = @_;
    my $html = '<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" /><iframe src="static/250ms_silence.mp3" allow="autoplay" id="audio" style="display:none"></iframe>';
    my $url = 'get_video?fmt=yt&id=' . uri_escape($request->{'qs'}{'id'});
    $url .= '&media=' . uri_escape($request->{'qs'}{'media'}) if($request->{'qs'}{'media'});
    if($request->{'qs'}{'media'} && ($request->{'qs'}{'media'} eq 'music')) {
        $request->{'path'}{'basename'} = 'ytaudio';
        $html .= '<audio controls autoplay src="' . $url . '">Great Browser</audio>';
    }
    else {
        $request->{'path'}{'basename'} = 'yt';
        $html .= '<video controls autoplay src="' . $url . '">Great Browser</video>';
    }
    return $html;
}

lib/MHFS/Plugin/Youtube.pm  view on Meta::CPAN

        return;
    }
    my $html = $self->searchbox($request);
    $html .= '<div id="vidlist">';
    foreach my $item (@{$json->{'items'}}) {
        my $id = $item->{'id'}{'videoId'};
        next if (! defined $id);
        $html .= '<div>';
        my $mediaurl = 'ytplayer?fmt=yt&id=' . $id;
        my $media =  $request->{'qs'}{'media'};
        $mediaurl .= '&media=' . uri_escape($media) if(defined $media);
        $html .= '<a href="' . $mediaurl . '">' . $item->{'snippet'}{'title'} . '</a>';
        $html .= '<br>';
        $html .= '<a href="' . $mediaurl . '"><img src="' . $item->{'snippet'}{'thumbnails'}{'default'}{'url'} . '" alt="Excellent image loading"></a>';
        $html .= ' <a href="https://youtube.com/channel/' . $item->{'snippet'}{'channelId'} . '">' .  $item->{'snippet'}{'channelTitle'} . '</a>';
        $html .= '<p>' . $item->{'snippet'}{'description'} . '</p>';
        $html .= '<br>-----------------------------------------------';
        $html .= '</div>'
    }
    $html .= '</div>';
    $html .= '<script>

lib/MHFS/Plugin/Youtube.pm  view on Meta::CPAN

    window.onpopstate = function(event) {
        console.log(event.state);
        vidlist.innerHTML = event.state;
    }
    </script>';
    $request->SendHTML($html);
}
sub onYoutube {
    my ($self, $request) = @_;
    my $evp = $request->{'client'}{'server'}{'evp'};
    my $youtubequery = 'q=' . (uri_escape($request->{'qs'}{'q'}) // '') . '&maxResults=' . ($request->{'qs'}{'maxResults'} // '25') . '&part=snippet&key=' . $self->{'settings'}{'Youtube'}{'key'};
    $youtubequery .= '&type=video'; # playlists not supported yet
    my $tosend = '';
    my @curlcmd = ('curl', '-G', '-d', $youtubequery, 'https://www.googleapis.com/youtube/v3/search');
    print "$_ " foreach @curlcmd;
    print "\n";
    state $tprocess;
    $tprocess = MHFS::Process->new(\@curlcmd, $evp, {
        'SIGCHLD' => sub {
            my $stdout = $tprocess->{'fd'}{'stdout'}{'fd'};
            my $buf;

lib/MHFS/Util.pm  view on Meta::CPAN

use strict; use warnings;
use feature 'say';
use Carp qw(croak);
use Exporter 'import';
use Feature::Compat::Try;
use File::Find;
use File::Basename;
use POSIX ();
use Cwd qw(abs_path getcwd);
use Encode qw(decode encode);
use URI::Escape qw(uri_escape uri_escape_utf8);
use MIME::Base64 qw(encode_base64url decode_base64url);
use PerlIO::encoding;
use warnings::register;
our @EXPORT_OK = ('LOCK_GET_LOCKDATA', 'LOCK_WRITE', 'UNLOCK_WRITE', 'write_file', 'write_text_file', 'write_text_file_lossy', 'read_file', 'read_text_file', 'read_text_file_lossy', 'shellcmd_unlock', 'ASYNC', 'FindFile', 'space2us', 'escape_html', '...

BEGIN {
    if (eval "use feature 'fc'; 1;") {
        *fold_case = \&CORE::fc;
    } else {
        *fold_case = \&lc;
    }
}

# single threaded locks

lib/MHFS/Util.pm  view on Meta::CPAN

        say "PID $pid ASYNC";
        return $pid;
    }
}

sub space2us {
    my ($string) = @_;
    $string =~ s/\s/_/g;
    return $string;
}
sub escape_html {
    my ($string) = @_;
    my %dangerchars = ( '"' => '&quot;', "'" => '&#x27;', '<' => '&lt;', '>' => '&gt;', '/' => '&#x2F;');
    $string =~ s/&/&amp;/g;
    foreach my $key(keys %dangerchars) {
        my $val = $dangerchars{$key};
        $string =~ s/$key/$val/g;
    }
    return \$string;
}

sub escape_html_noquote {
    my ($string) = @_;
    my %dangerchars = ('<' => '&lt;', '>' => '&gt;');
    $string =~ s/&/&amp;/g;
    foreach my $key(keys %dangerchars) {
        my $val = $dangerchars{$key};
        $string =~ s/$key/$val/g;
    }
    return \$string;
}

sub pid_running {
    return kill 0, shift;
}

sub shell_escape {
    my ($cmd) = @_;
    ($cmd) =~ s/'/'"'"'/g;
    return $cmd;
}

sub output_dir_versatile {
    my ($path, $options) = @_;
    # hide the root path if desired
    my $root = $options->{'root'};
    $options->{'min_file_size'} //= 0;

lib/MHFS/Util.pm  view on Meta::CPAN

    }

    return $res;
}

# save space by not precent encoding valid UTF-8 characters
sub small_url_encode {
    my ($octets) = @_;
    say "before $octets";

    my $escapedoctets = ${escape_html($octets)};
    my $res;
    while(length($escapedoctets)) {
        $res .= decode('UTF-8', $escapedoctets, Encode::FB_QUIET);
        last if(!length($escapedoctets));
        my $oct = ord(substr($escapedoctets, 0, 1, ''));
        $res .= sprintf ("%%%02X", $oct);
    }
    say "now: $res";
    return $res;
}

sub uri_escape_path {
    my ($b_path) = @_;
    uri_escape($b_path, qr/[^A-Za-z0-9\-\._~\/]/)
}

sub uri_escape_path_utf8 {
    my ($path) = @_;
    uri_escape_utf8($path, qr/[^A-Za-z0-9\-\._~\/]/)
}

sub round {
    return int($_[0]+0.5);
}

sub ceil_div {
    return int(($_[0] + $_[1] - 1) / $_[1]);
}

share/public_html/static/hls.js  view on Meta::CPAN

    }
  }]);

  return Fragment;
}();

/* harmony default export */ var loader_fragment = (fragment_Fragment);
// CONCATENATED MODULE: ./src/utils/attr-list.js
function attr_list__classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/; // eslint-disable-line no-useless-escape
var ATTR_LIST_REGEX = /\s*(.+?)\s*=((?:\".*?\")|.*?)(?:,|$)/g; // eslint-disable-line no-useless-escape

// adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js

var AttrList = function () {
  function AttrList(attrs) {
    attr_list__classCallCheck(this, AttrList);

    if (typeof attrs === 'string') {
      attrs = AttrList.parseAttrList(attrs);
    }

share/public_html/static/music_inc/src/miniaudio.h  view on Meta::CPAN


    return frameCount;
}


static MA_INLINE float ma_noise_f32_brownian(ma_noise* pNoise, ma_uint32 iChannel)
{
    double result;
    
    result = (ma_lcg_rand_f64(&pNoise->lcg) + pNoise->state.brownian.accumulation[iChannel]);
    result /= 1.005; /* Don't escape the -1..1 range on average. */

    pNoise->state.brownian.accumulation[iChannel] = result;
    result /= 20;

    return (float)(result * pNoise->config.amplitude);
}

static MA_INLINE ma_int16 ma_noise_s16_brownian(ma_noise* pNoise, ma_uint32 iChannel)
{
    return ma_pcm_sample_f32_to_s16(ma_noise_f32_brownian(pNoise, iChannel));

share/public_html/static/music_worklet_inprogress/decoder/deps/miniaudio/miniaudio.h  view on Meta::CPAN


    return frameCount;
}


static MA_INLINE float ma_noise_f32_brownian(ma_noise* pNoise, ma_uint32 iChannel)
{
    double result;

    result = (ma_lcg_rand_f64(&pNoise->lcg) + pNoise->state.brownian.accumulation[iChannel]);
    result /= 1.005; /* Don't escape the -1..1 range on average. */

    pNoise->state.brownian.accumulation[iChannel] = result;
    result /= 20;

    return (float)(result * pNoise->config.amplitude);
}

static MA_INLINE ma_int16 ma_noise_s16_brownian(ma_noise* pNoise, ma_uint32 iChannel)
{
    return ma_pcm_sample_f32_to_s16(ma_noise_f32_brownian(pNoise, iChannel));

share/public_html/static/music_worklet_inprogress/index.html  view on Meta::CPAN

    fetch(myRequest).then(function(response) {
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.text();        
    }).then((html) => {
        document.getElementById("musicdb").innerHTML = html;
    });
    */

    function escapeHTML(unsafe) {
    return unsafe
         .replace(/&/g, "&amp;")
         .replace(/</g, "&lt;")
         .replace(/>/g, "&gt;")
         .replace(/"/g, "&quot;")
         .replace(/'/g, "&#039;");
    }

    function dlurl(apiroot, namestack, name) {
        let str = apiroot + '/music_dl?action=dl&name=';
        let fullname = '';
        namestack.forEach(function(elm) {
            fullname += elm + '/';
        });
        fullname += name;
        return str+encodeURIComponent(fullname);        
    }

    function trackHTML(apiroot, namestack, name) {
        let text = '<tr class="track"><td>' + escapeHTML(name) + '</td>';        
        text += '<td><a href="#">Play</a></td><td><a href="#">Queue</a></td><td><a href="';
        text += dlurl(apiroot, namestack, name) + '">DL</a></td></tr>'; 
        return text;
    }

    const OldDB2HTML = function(json) {
        let text = '';
        let namestack = [];
        let files = json.files;        
        while(files.length > 0) {

share/public_html/static/music_worklet_inprogress/index.html  view on Meta::CPAN

                text += '</tbody></table></td></tr>';
                if((namestack.length === 0)) {
                    text += '<br>';
                }

                continue;
            }
            // is directory
            else if(file.files) {
                text += '<tr><td><table border="1" class="tbl_track"><tbody><tr class="track"><th>';
                text += escapeHTML(file.name) + '</th><th><a href="#">Play</a></th><th><a href="#">Queue</a></th><th><a href="';
                text += dlurl(apiroot, namestack, file.name) + '">DL</a></th></tr>';
                namestack.push(file.name);     
                file.files.push(null);
                file.files.push(...files);
                files = file.files;
            }
            else {
                // single track without dir
                if(namestack.length === 0) {
                    text += '<table border="1" class="tbl_track"><tbody>';                    

share/public_html/static/music_worklet_inprogress/index.html  view on Meta::CPAN

    const DB2HTML = function(node, currentPath) {
        let text = '';
        let tiopen = '<td>';
        let ticlose = '</td>';
        if(!currentPath || node.files) {
            text += '<table border="1" class="tbl_track"><tbody>';
            tiopen = '<th>';
            ticlose = '</th>';
        }
        text += '<tr class="track">';
        text += tiopen +escapeHTML(node.name) + ticlose + tiopen + '<a href="#">Play</a>' + ticlose + tiopen + '<a href="#">Queue</a>' + ticlose + tiopen + '<a href="';
        text += DB2HTMLURL(currentPath+node.name) + '">DL</a>' + ticlose + '</tr>';
        if(node.files) {
            for(const file of node.files) {
                text += DB2HTML(file, currentPath+node.name);
            }
        }
        if(!currentPath || node.files) {
            text += '</tbody></table>';
        }
        return text;

t/01-util.t  view on Meta::CPAN

#!perl
use 5.014;
use strict;
use warnings;
use Test2::V0;
use Feature::Compat::Try;
use Encode qw(decode encode);
use MHFS::Util qw(space2us escape_html escape_html_noquote shell_escape get_printable_utf8 read_text_file_lossy read_text_file write_text_file write_text_file_lossy decode_utf_8 parse_ipv4 write_file read_file fold_case);

plan 103;

is(space2us('hello world'), 'hello_world');

my $unsafe_chars = q|"'<>/|;
is(${escape_html($unsafe_chars)}, '&quot;&#x27;&lt;&gt;&#x2F;');
is(${escape_html_noquote($unsafe_chars)}, q|"'&lt;&gt;/|);

is(shell_escape(q|it's|), q|it'"'"'s|);

{
    my $message = 'valid ip parses';
    try {
        is(parse_ipv4('8.8.8.8'), 8 | (8 << 8) | (8 << 16) | (8 << 24), $message);
        is(parse_ipv4('255.255.255.255'), 0xFFFFFFFF, $message);
    } catch ($e) {
        fail($message);
    }
}



( run in 1.159 second using v1.01-cache-2.11-cpan-39bf76dae61 )