/dev/null#m", "$interval * * * * $root/../bin/refresh_youtube.pl &2> /dev/null", $crontab); } else { $crontab .= "$interval * * * * $root/../bin/refresh_youtube.pl &2> /dev/null\n"; } file_put_contents("$root/.crontab.tmp", $crontab); error(shell_exec(escapeshellcmd("/usr/bin/crontab $root/.crontab.tmp"))); error(shell_exec(escapeshellcmd("/bin/rm $root/.crontab.tmp"))); } # Refresh feeds function refresh() { global $root; return shell_exec(escapeshellcmd("$root/../bin/refresh_youtube.pl")); } # Fix quotes for SQL input # Convert ip4 to a long int for comparison in permissions function ip4_2_long($ip) { $ips = preg_split('/\./',$ip); return($ips[3] | $ips[2] << 8 | $ips[1] << 16 | $ips[0] << 24); } # Convert ip6 to a long int for comparison in permissions function ip6_2_long($ip) { if (!function_exists('gmp_init') && !function_exists('bcadd')) { error('
Requires either GMP or BCMATH extension
'); } $ip_n = inet_pton($ip); $bin = ''; for ($bit = strlen($ip_n) - 1; $bit >= 0; $bit--) { $bin = sprintf('%08b', ord($ip_n[$bit])) . $bin; } if (function_exists('gmp_init')) { return gmp_strval(gmp_init($bin, 2), 10); } elseif (function_exists('bcadd')) { $dec = '0'; for ($i = 0; $i < strlen($bin); $i++) { $dec = bcmul($dec, '2', 0); $dec = bcadd($dec, $bin[$i], 0); } return $dec; } else { exit(); trigger_error('GMP or BCMATH extension not installed!', E_USER_ERROR); } } # Check if given IP is in a ip4 CIDR range function in_range4($allowed,$cidr,$ip) { if ( ( ( ip4_2_long($ip) >= ip4_2_long($allowed) ) && ( ip4_2_long($ip) <= (ip4_2_long($allowed)+2**(32-$cidr)) ) ) || ($ip == $_SERVER['SERVER_ADDR'])) { return 1; } else { return 0; } } # Check if write is outside of read range function write_outside_read4($read4ip,$read4cidr,$write4ip,$write4cidr) { $readtop = ip4_2_long($read4ip)+2**(32-$read4cidr); $writetop = ip4_2_long($write4ip)+2**(32-$write4cidr); if (!in_range4($read4ip,$read4cidr,$write4ip) || ($writetop > $readtop)) { error("write range ($write4ip/$write4cidr) must be contained within read range ($read4ip/$read4cidr)
"); return 1; } return 0; } # Check if given IP is in a ip6 CIDR range function in_range6($allowed,$cidr,$ip) { if ( ( ( ip6_2_long($ip) >= ip6_2_long($allowed) ) && ( ip6_2_long($ip) <= (ip6_2_long($allowed)+2**(32-$cidr)) ) ) || ($ip == $_SERVER['SERVER_ADDR'])) { return 1; } else { return 0; } } # Strip argument key and value from current target URL and return valid updated URL function remove_arg($target,$arg) { global $debug_text; if (preg_match("/\?$arg/",$target)) { # First argument, leave ? and remove trailing & if it exists $target = preg_replace("/$arg=[^&]*&?/",'',$target); if (preg_match("/\?$/",$target)) { $target = rtrim($target,"?"); } } else { # Secondary argument, remove leading & $target = preg_replace("/&$arg=[^&]*/",'',$target); } $debug_text .= "Just removed $arg
Target is now: $target
"; return $target; } # Return whether the client IP is ip4 or ip6 function ip_version($ip) { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return 4; } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { return 6; } else { return 0; } } # Return read and write permissions given client IP and preferences function get_permissions($settings) { $permissions = array(); if ($settings['ipv'] == 4) { $permissions['rip'] = preg_replace("/([\d\.]+)\/\d+/","$1",$settings['read4']); $permissions['wip'] = preg_replace("/([\d\.]+)\/\d+/","$1",$settings['write4']); $permissions['rcidr'] = preg_replace("/[\d\.]+\/(\d+)/","$1",$settings['read4']); $permissions['wcidr'] = preg_replace("/[\d\.]+\/(\d+)/","$1",$settings['write4']); if ($settings['enable4']) { $permissions['readable'] = in_range4($permissions['rip'],$permissions['rcidr'],$_SERVER['REMOTE_ADDR']); $permissions['writable'] = in_range4($permissions['wip'],$permissions['wcidr'],$_SERVER['REMOTE_ADDR']); } else { $permissions['readable'] = 0; $permissions['writable'] = 0; } } else { $permissions['rip'] = preg_replace("/([\d:]+)\/\d+/","$1",$settings['read6']); $permissions['wip'] = preg_replace("/([\d:]+)\/\d+/","$1",$settings['write6']); $permissions['rcidr'] = preg_replace("/[\d:]+\/(\d+)/","$1",$settings['read6']); $permissions['wcidr'] = preg_replace("/[\d:]+\/(\d+)/","$1",$settings['write6']); if ($settings['enable6']) { $permissions['readable'] = 1; //$permissions['readable'] = in_range6($permissions['rip'],$permissions['rcidr'],$_SERVER['REMOTE_ADDR']); $permissions['writable'] = 0; //$permissions['writable'] = in_range6($permissions['wip'],$permissions['wcidr'],$_SERVER['REMOTE_ADDR']); } else { $permissions['readable'] = 0; $permissions['writable'] = 0; } } return $permissions; } # Print one row given the video details function print_video($video,$settings,$mobile,$show_seen) { $html = 'PHP does not support IPv6 and your address is: ' . $_SERVER['REMOTE_ADDR'] . '
'); # Reject connection if not a valid ip4 or ip6 address (what else is it?) } else { error('Invalid remote IP address: ' . $_SERVER['REMOTE_ADDR'] . '
'); } } # Reject connection if not readable if (!$permissions['readable']) { error('You do not have readable access to this page
'); if ($settings['ipv'] == 4) { if ($settings['enable4']) { error('Your IP (' . $_SERVER['REMOTE_ADDR'] . ') falls outside of readable range (' . $settings['read4'] . ')
'); } else { error('IPv4 Interface is disabled
'); } } else { if ($settings['enable6']) { error('Your IP (' . $_SERVER['REMOTE_ADDR'] . ') falls outside of readable range (' . $settings['read6'] . ')
'); } else { error('IPv6 Interface is disabled
'); } } } # Collect URL arguments $target = $_SERVER['REQUEST_URI']; $args = array(); parse_str($_SERVER['QUERY_STRING'], $args); # Some arguments will require the page to be reloaded after processing. # They will set this flag which will be checked after all arguements are done. $reload = 0; ## STARTREAD: Read options included prior to the ENDREAD line if (isset($args['mobile'])) { if ($args['mobile'] != '' && $args['mobile'] != '0') { $mobile = 1; } else { $mobile = 0; } $target = remove_arg($target,'mobile'); unset($args['mobile']); } # Retrieve passed error message if (isset($args['msg'])) { if ($args['msg'] != '') { error(urldecode($args['msg'])); } $target = remove_arg($target,'msg'); unset($args['msg']); } # Add parameters to SQL query to restrict by search results if (isset($args['search'])) { if (isset($args['search_clear']) || $args['search'] == '') { unset($args['search_clear']); $target = remove_arg($target,'search_clear'); $target = remove_arg($target,'search'); } else { $search = urldecode($args['search']); # If search is quoted with '/', treat it as a regex if (preg_match('#^/.*/$#',$search)) { $sql_search = preg_replace('#^/(.*)/$#', '$1', $search); $regex_search = $handle->loadExtension('pcre.so'); if ($regex_search) { $sql_search = " videos.title REGEXP '" . $sql_search . "'"; } else { # emulate regex if module is unavailable $new_search = ''; if (preg_match('/^\^/', $sql_search)) { $start = preg_replace('/^\^([^\.]*).*/','$1',$sql_search); $sql_search = preg_replace('/^\^[^\.]*(.*)$/','$1',$sql_search); $new_search .= " AND videos.title LIKE '" . $start . "%'"; } if (preg_match('/\$$/', $sql_search)) { $tail = preg_replace('/^.*?([^\*]*)\$$/','$1',$sql_search); $sql_search = preg_replace('/^(.*?)[^\*]*\$$/','$1',$sql_search); $new_search .= " AND videos.title LIKE '%" . $tail . "'"; } $sql_search = preg_replace('/(\.\*)?(.*)(\.\*)?/','$2',$sql_search); $searches = preg_split('/\.\*/', $sql_search); foreach ($searches as $s) { if ($s != '') { $new_search .= " AND videos.title LIKE '%" . $s . "%'"; } } $sql_search = preg_replace('/ AND(.*)/', '$1', $new_search); error("You do not have the pcre.so extension for SQLite3."); if ($permissions['writable']) { error("You will need to add the path to your extensions directory with an argument in your php.ini file like:
sqlite3.extension_dir = \"/usr/lib/sqlite3/\"
"); } error ("Simulating basic wildcard (.*) and anchor (^$) expression:
$sql_search
"); } } else { $sql_search = " videos.title LIKE '%" . sql_escape($search) . "%'"; } } unset($args['search']); $target = remove_arg($target,'search'); } # Set flag to not hide videos marked as seen from SQL query results if (isset($args['show_seen'])) { $show_seen = $args['show_seen']; unset($args['show_seen']); $target = remove_arg($target,'show_seen'); } # Remove previous filter arguments to revert to regular query results if (isset($args['filter_clear'])) { unset($args['filter_clear']); $target = remove_arg($target,'filter_clear'); unset($args['filter']); $target = remove_arg($target,'filter'); unset($args['filter[]']); $target = remove_arg($target,urlencode('filter[]')); } # Add claus(es) to SQL query to limit by channel or category if (isset($args['filter'])) { $categories = array(); if (!is_array($args['filter'])) { if (preg_match('/^UC[0-9a-zA-Z\_\-]{22}$/', $args['filter'])) { $chan_filter = $args['filter']; } elseif ($args['filter'] != '-') { $categories[0] = $args['filter']; } $target = remove_arg($target,'filter'); $target = remove_arg($target,urlencode('filter[]')); } else { foreach ($args['filter'] as $filter) { $categories[] = urldecode($filter); $target = remove_arg($target,urlencode('filter[]')); } $target = remove_arg($target,'filter'); } unset($args['filter']); unset($args['filter[]']); } ## ENDREAD # If there are remaining arguments, ensure that the client has write permissions if ($permissions['writable']) { if (sizeof($args) > 0) { $reload = 1; } } elseif ($permissions['readable']) { if (sizeof($args) > 0) { $reload = 1; error("You are allowed to read but not write. However, a write argument was passed.
"); unset($args); $target = preg_replace('/^([^\?]*)\?.*/','$1',$_SERVER['REQUEST_URI']); } } else { error('Initializing database...
"; $debug_text .= "" . initialize() . "
"; $reload = 1; $target = remove_arg($target,'initialize'); $handle = new SQLite3($database); $no_db = 0; $args['refresh'] = 15; } # Delete database if (isset($args['delete_db'])) { $debug_text .= "Deleting database
"; $err = delete_db(); if ($err) { error("Failed to delete db: $err
"); } else { $debug_text .= "Database deleted
"; } } # Handle all change requests. if ( !$no_db && $reload && $permissions['writable'] ) { # Force feeds to refresh if (isset($args['force_refresh']) && $args['force_refresh'] == 1) { $debug_text .= "Refreshing feeds...
"; # Must free database in order to insert $handle->close(); $debug_text .= "" . refresh() . "
"; $handle = new SQLite3($database); $target = remove_arg($target,'force_refresh'); $reload = 1; } # Mark one or all videos as played. if (isset($args['rm'])) { if (preg_match("/^([^']{11})$/",urldecode($args['rm']))) { $debug_text .= "Removing " . urldecode($args['rm']) . "
"; $rm = sql_escape(urldecode($args['rm'])); $handle->query("UPDATE videos SET seen = 1 WHERE videoId = '$rm'"); } elseif (urldecode($args['rm']) == '*') { $debug_text .= "Removing All
"; $handle->query("UPDATE videos SET seen = 1 WHERE seen = 0"); } $target = remove_arg($target,'rm'); } # Mark one or all videos as played. if (isset($args['unrm'])) { if (preg_match("/^([^']{11})$/",urldecode($args['unrm']))) { $debug_text .= "Restoring " . urldecode($args['unrm']) . "
"; $unrm = sql_escape(urldecode($args['unrm'])); $handle->query("UPDATE videos SET seen = 0 WHERE videoId = '$unrm'"); } $target = remove_arg($target,'unrm'); } # Update the refresh interval if (isset($args['refresh'])) { $interval = urldecode($args['refresh']); if (preg_match('/^[0-9]+$/',$interval) && $interval <= 60) { $handle->query("UPDATE settings SET refresh = '$interval'"); update_cron($interval); $debug_text .= "Set update interval to $interval
"; } else { error("Update interval must be an integer lower than or equal to 60, $interval is not
"); } $target = remove_arg($target,'refresh'); } # Update the theme if (isset($args['theme'])) { $found = 0; $theme = urldecode($args['theme']); foreach (glob($root.'/css/*.css') as $file) { $file = preg_replace('/.*\/([^\/]*)\.css$/','\1',$file); if ($args['theme'] == $file && $file != 'style') { $handle->query("UPDATE settings SET theme = '" . sql_escape($theme) . "'"); $debug_text .= "Set theme $theme
"; $found = 1; } } if (!$found) { error("" . $theme . " is not a valid theme option
"); } $target = remove_arg($target,'theme'); } # Update the thumbnail quality if (isset($args['thumbnail_quality'])) { if (preg_match("/^(maxresdefault|sddefault|hqdefault|mqdefault|default|1|2|3)$/",urldecode($args['thumbnail_quality']))) { $thumbnail_quality = sql_escape(urldecode($args['thumbnail_quality'])); if (!isset($thumbnail_quality) || $thumbnail_quality == '') { $thumbnail_quality = 'default'; } $thumbnail_quality = sql_escape(urldecode($args['thumbnail_quality'])); $handle->query("UPDATE settings SET thumbnail_quality = '$thumbnail_quality'"); $debug_text .= "Set thumbnail_quality $thumbnail_quality
"; } else { error("" . $args['thumbnail_quality'] . " is not a valid thumbnail_quality option
"); } $target = remove_arg($target,'thumbnail_quality'); } # Update the embedded player type if (isset($args['embed_type'])) { if (preg_match("/^(embed|nocookie|proxy)$/",urldecode($args['embed_type']))) { $embed_type = sql_escape(urldecode($args['embed_type'])); $handle->query("UPDATE settings SET embed_type = '$embed_type'"); $debug_text .= "Set embed_type $embed_type
"; } else { error("" . $args['embed_type'] . " is not a valid embed_type option
"); } $target = remove_arg($target,'embed_type'); } # Update the default player if (isset($args['player'])) { if (preg_match("/^(clip|embed|web)$/",urldecode($args['player']))) { $player = sql_escape(urldecode($args['player'])); $handle->query("UPDATE settings SET player = '$player'"); $debug_text .= "Set player $player
"; } else { error("" . $args['player'] . " is not a valid player option
"); } $target = remove_arg($target,'player'); } # Subscribe to a new channel if (isset($args['sub'])) { $debug_text .= "Adding " . urldecode($args['sub']) . "...
"; # Must free database in order to insert $handle->close(); $debug_text .= "" . add_channel(urldecode($args['sub'])) . "
"; $handle = new SQLite3($database); $reload = 1; $target = remove_arg($target,'sub'); } # Unsubscribe and remove channel's videos. if (isset($args['unsub'])) { $unsub = sql_escape(urldecode($args['unsub'])); $channels = $handle->query("SELECT * FROM channels WHERE channelId = '$unsub'"); $chan = $channels->fetchArray(); $debug_text .= "Removing " . $chan['channelName'] . "
"; $handle->query("DELETE FROM channels WHERE channelId = '$unsub'"); $handle->query("DELETE FROM videos WHERE channelId = '$unsub'"); $target = remove_arg($target,'unsub'); } # Update IPv4 writeable range if (isset($args['write4'])) { $write4ip = preg_replace("/([\d\.]+)\/\d+/","$1",urldecode($args['write4'])); $write4cidr = preg_replace("/[\d\.]+\/(\d+)/","$1",urldecode($args['write4'])); if (isset($args['read4'])) { $read4ip = preg_replace("/([\d\.]+)\/\d+/","$1",urldecode($args['read4'])); $read4cidr = preg_replace("/[\d\.]+\/(\d+)/","$1",urldecode($args['read4'])); } else { $read4ip = preg_replace("/([\d\.]+)\/\d+/","$1",$settings['read4']); $read4cidr = preg_replace("/[\d\.]+\/(\d+)/","$1",$settings['read4']); } if ( write_outside_read4($read4ip,$read4cidr,$write4ip,$write4cidr) ) { error("Invalid read/write. Write must be completely contained within read.
"); $target = remove_arg($target,'write4'); $target = remove_arg($target,'read4'); unset($args['read4']); } elseif ( in_range4($write4ip,$write4cidr,$_SERVER['REMOTE_ADDR']) ) { $handle->query("UPDATE settings SET write4 = '$write4ip/$write4cidr'"); $debug_text .= "Updated writable IPv4 to $write4ip/$write4cidr
"; $target = remove_arg($target,'write4'); } else { error("You are not allowed to remove write access from yourself. " . $_SERVER['REMOTE_ADDR'] . " is not in range $write4ip/$write4cidr
"); $target = remove_arg($target,'write4'); } } # Update IPv4 readable range if (isset($args['read4'])) { # Update whether IPv4 is enabled if (isset($args['enable4']) && $args['enable4'] == 'on') { if (!$settings['enable4']) { $debug_text .= "Enabling IPv4
"; $handle->query("UPDATE settings SET enable4 = 1"); } } else { if ($settings['ipv'] == 4) { error("You are currently using the IPv4 interface (Your IP: " . $_SERVER['REMOTE_ADDR'] . "). You cannot remove access from yourself.
"); } else { $debug_text .= "Disabling IPv4
"; #$handle->query("UPDATE settings SET enable4 = 0"); } } $target = remove_arg($target,'enable4'); $read4ip = preg_replace("/([\d\.]+)\/\d+/","$1",urldecode($args['read4'])); $read4cidr = preg_replace("/[\d\.]+\/(\d+)/","$1",urldecode($args['read4'])); if ( in_range4($read4ip,$read4cidr,$_SERVER['REMOTE_ADDR']) ) { $handle->query("UPDATE settings SET read4 = '$read4ip/$read4cidr'"); $debug_text .= "Updated readable IPv4 to $read4ip/$read4cidr
"; $target = remove_arg($target,'read4'); } else { error("You are not allowed to remove read access from yourself. " . $_SERVER['REMOTE_ADDR'] . " is not in range $read4ip/$read4cidr
"); $target = remove_arg($target,'read4'); } } # Update IPv6 readable range if (isset($args['read6'])) { # Update whether IPv6 is enabled if (isset($args['enable6']) && $args['enable6'] == 'on') { if (!$settings['enable6']) { $debug_text .= "Enabling IPv6
"; $handle->query("UPDATE settings SET enable6 = 1"); } } else { if ($settings['ipv'] == 6) { error("You are currently using the IPv6 interface (Your IP: " . $_SERVER['REMOTE_ADDR'] . "). You cannot remove access from yourself.
"); } else { $debug_text .= "Disabling IPv6
"; $handle->query("UPDATE settings SET enable6 = 0"); } } $target = remove_arg($target,'enable6'); $handle->query("UPDATE settings SET read6 = '" . urldecode($args['read6']) . "'"); $debug_text .= "Updated readable IPv6 to " . $args['read6'] . "
"; $target = remove_arg($target,'read6'); } if (isset($args['write6'])) { $handle->query("UPDATE settings SET write6 = '" . urldecode($args['write6']) . "'"); $debug_text .= "Updated writable IPv6 to " . $args['write6'] . "
"; $target = remove_arg($target,'write6'); } # Update the Category for channel if (isset($args['category'])) { # URL arg is formatted as 'channelId category,other,third' $category_array = preg_split('/\ /',urldecode($args['category'])); foreach ($category_array as $item) { # First item is the channelId if (!isset($category_chan)) { $category_chan = $item; # All others are categories } elseif (!isset($category_name)) { $category_name = $item; # Restore spaces removed by split } else { $category_name .= ' ' . $item; } } $category_chan = sql_escape($category_chan); $category_name = sql_escape($category_name); if ($category_name != '') { $category_name = ','.$category_name.','; } $debug_text .= 'Add Category - Channel: ' . $category_chan . ' Category: ' . $category_name . '
'; $handle->query("UPDATE channels SET category = '$category_name' WHERE channelId = '$category_chan'"); $debug_text .= 'Category updated
'; $target = remove_arg($target,'category'); } # Update the Title Filter RegExp for a channel if (isset($args['regex'])) { $regex_array = preg_split('/\ /',urldecode($args['regex'])); foreach ($regex_array as $item) { if (!isset($regex_chan)) { $regex_chan = $item; } elseif (!isset($regex_exp)) { $regex_exp = $item; } else { $regex_exp .= ' ' . $item; } } $regex_chan = sql_escape($regex_chan); $regex_exp = sql_escape($regex_exp); $debug_text .= 'Add Regex - Channel: ' . $regex_chan . ' Exp: ' . $regex_exp . '
'; $handle->query("UPDATE channels SET regex = '$regex_exp' WHERE channelId = '$regex_chan'"); $debug_text .= 'Regex updated
'; $target = remove_arg($target,'regex'); } # If there are remaining arguments, they are invalid if (preg_match("/[^\?]*\?(.*)/",$target)) { $remaining = preg_replace("/^(.)*\?(.*)$/",'$2',$target); $debug_text .= "Remaining: $remaining
"; $bad_args = preg_split("/\&/",$remaining); foreach ($bad_args as $arg) { $value = preg_replace("/[^=]*=([^=]*)/","$1",$arg); $arg = preg_replace("/([^=]*)=[^=]*/","$1",$arg); error('Ignoring invalid argument: ' . urldecode($arg) . ' with value ' . urldecode($value) . '
'); $target = remove_arg($target,$arg); } } } # Clean up target URL given intact arguments $target = preg_replace('/[\?&]$/', '', $target); # Close DB then refresh the page with what should be a clean URL if ($reload) { if ($debug && isset($debug_text) && $debug_text != '') { $debug_text .= ' '; $debug_text = 'It appears that you have not yet initialized your subscriptions database. You can do this with the button below.
Note that this will create an empty database with default settings. The default settings allow read and write access from all IPs, so you will probably want to update the settings immediately. This can be done using the cog icon in the top right corner.
Use the + menu above to add new subscriptions
This box will accept a variety of inputs including:
An unknown error has occurred