Skip to content

Commit

Permalink
Add logic to help prevent lyteCache.php being (ab)used, includes some…
Browse files Browse the repository at this point in the history
… spaces/ quotes cleanup lyteCache.php.
  • Loading branch information
futtta committed Sep 28, 2020
1 parent 43bd4c2 commit f38e4d1
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 89 deletions.
172 changes: 84 additions & 88 deletions lyteCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,67 +23,55 @@
define( 'LYTE_CACHE_DIR', WP_CONTENT_DIR .'/'. LYTE_CACHE_CHILD_DIR );
}

$lyte_thumb_error = "";
$lyte_thumb_dontsave = "";
$thumbContents = "";
$lyte_thumb_error = '';
$lyte_thumb_dontsave = '';
$thumbContents = '';
$lyte_thumb_report_err = false;

/*
* step 1: get vid ID (or full thumbnail URL) from request and validate
*/

$origThumbURL = get_origThumbUrl();

// should we output debug info in a header?
if ( array_key_exists("reportErr", $_GET) ) {
if ( array_key_exists( 'reportErr', $_GET ) ) {
$lyte_thumb_report_err = true;
}

// get thumbnail-url from request
if ( array_key_exists("origThumbUrl", $_GET) && $_GET["origThumbUrl"] !== "" ) {
$origThumbURL = urldecode($_GET["origThumbUrl"]);
} else {
// faulty request, force a grey background
$origThumbURL = "https://i.ytimg.com/vi/thisisnotavalidvid/hqdefault.jpg";
}

// make sure the thumbnail-url is for youtube
$origThumbDomain = parse_url($origThumbURL, PHP_URL_HOST);
if ( str_replace( array("ytimg.com","youtube.com","youtu.be"), "", $origThumbDomain ) === $origThumbDomain ) {
// faulty request, force a grey background
$origThumbURL = "https://i.ytimg.com/vi/thisisnotavalidvid/hqdefault.jpg";
}
/*
* step 2: check if it is safe to serve from cache and redirect if not.
*/

// make sure the thumbnail-url is for an image (.jpg)
$origThumbPath = parse_url($origThumbURL, PHP_URL_PATH);
if ( lyte_str_ends_in( $origThumbPath, ".jpg" ) !== true ) {
// faulty request, force a grey background
$origThumbURL = "https://i.ytimg.com/vi/thisisnotavalidvid/hqdefault.jpg";
if ( ! file_exists( LYTE_CACHE_DIR ) || ( file_exists( LYTE_CACHE_DIR . '/doubleCheckLyteThumbnailCache.txt' ) && ! array_key_exists( 'lyteCookie', $_COOKIE ) ) ) {
// local thumbnail caching not on or no cookie found, redirect to original at youtube.
$lyte_thumb_error = 'possible hotlinker/';
lyte_thumb_fallback();
}

// TODO: extra checks to prevent automated hotlinking abuse?
/*
* step 2: check for and if need be create wp-content/cache/lyte_thumbs
* step 3: check for and if need be create wp-content/cache/lyte_thumbs
*/

if ( lyte_check_cache_dir(LYTE_CACHE_DIR) === false ) {
if ( lyte_check_cache_dir( LYTE_CACHE_DIR ) === false ) {
$lyte_thumb_dontsave = true;
$lyte_thumb_error .= "checkcache fail/ ";
$lyte_thumb_error .= 'checkcache fail/ ';
}

/*
* step 3: if not in cache: fetch from YT and store in cache
* step 4: if not in cache: fetch from YT and store in cache
*/

if ( strpos($origThumbURL,'http') !== 0 && strpos($origThumbURL,'//') === 0 ) {
$origThumbURL = 'https:'.$origThumbURL;
}

$localThumb = LYTE_CACHE_DIR . '/' . md5($origThumbURL) . ".jpg";
$localThumb = LYTE_CACHE_DIR . '/' . md5($origThumbURL) . '.jpg';
$expiryTime = filemtime( $localThumb ) + 3*24*60*60; // images can be cached for 3 days.
$now = time();

if ( !file_exists( $localThumb ) || $lyte_thumb_dontsave || ( file_exists( $localThumb ) && $expiryTime < $now ) ) {
$thumbContents = lyte_get_thumb($origThumbURL);
$thumbContents = lyte_get_thumb( $origThumbURL );

if ( $thumbContents != '' && ! $lyte_thumb_dontsave ) {
// save file but immediately check if it is a jpeg and delete if not.
Expand All @@ -97,40 +85,40 @@
}

/*
* step 4: serve img
* step 5: serve img
*/

if ( $thumbContents == "" && !$lyte_thumb_dontsave && file_exists($localThumb) && is_jpeg($localThumb) ) {
if ( $thumbContents == '' && ! $lyte_thumb_dontsave && file_exists( $localThumb ) && is_jpeg( $localThumb ) ) {
$thumbContents = file_get_contents( $localThumb );
} else {
$lyte_thumb_error .= "not from cache/ ";
$lyte_thumb_error .= 'not from cache/ ';
}

if ( $thumbContents != "") {
if ( $lyte_thumb_error !== "" && $lyte_thumb_report_err ) {
if ( $thumbContents != '') {
if ( $lyte_thumb_error !== '' && $lyte_thumb_report_err ) {
header('X-lyte-error: '.$lyte_thumb_error);
}

$modTime=filemtime($localThumb);
$modTime = filemtime($localThumb);

date_default_timezone_set("UTC");
$modTimeMatch = (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $modTime);
date_default_timezone_set('UTC');
$modTimeMatch = ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) && strtotime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) === $modTime );

if ( $modTimeMatch ) {
header('HTTP/1.1 304 Not Modified');
header('Connection: close');
} else {
// send all sorts of headers
$expireTime=60*60*24*7; // 1w
header('Content-Length: '.strlen($thumbContents));
header('Cache-Control: max-age='.$expireTime.', public, immutable');
header('Expires: '.gmdate('D, d M Y H:i:s', time() + $expireTime).' GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $modTime).' GMT');
header('Content-type:image/jpeg');
$expireTime = 60 * 60 * 24 * 7; // 1w
header( 'Content-Length: '. strlen( $thumbContents) );
header( 'Cache-Control: max-age=' . $expireTime . ', public, immutable' );
header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $expireTime).' GMT' );
header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $modTime) . ' GMT' );
header( 'Content-type:image/jpeg' );
echo $thumbContents;
}
} else {
$lyte_thumb_error .= "no thumbContent/ ";
$lyte_thumb_error .= 'no thumbContent/ ';
lyte_thumb_fallback();
}

Expand All @@ -155,68 +143,76 @@ function is_jpeg( $in ) {

function lyte_check_cache_dir( $dir ) {
// Try creating the dir if it doesn't exist.
if ( ! file_exists( $dir ) ) {
@mkdir( $dir, 0775, true );
if ( ! file_exists( $dir ) ) {
return false;
}
}

// If we still cannot write, bail.
if ( ! is_writable( $dir ) ) {
if ( ! file_exists( $dir ) || ! is_writable( $dir ) ) {
return false;
}

// Create an index.html in there to avoid prying eyes!
$idx_file = rtrim( $dir, '/\\' ) . '/index.html';
if ( ! is_file( $idx_file ) ) {
@file_put_contents( $idx_file, '<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/wp-youtube-lyte/" rel="nofollow">WP YouTube Lyte</a></body></html>' );
}

return true;
}

function lyte_str_ends_in($haystack,$needle) {
$needleLength = strlen($needle);
$haystackLength = strlen($haystack);
$lastPos=strrpos($haystack,$needle);
if ($lastPos === $haystackLength - $needleLength) {
return true;
} else {
return false;
}
}

function lyte_get_thumb($thumbUrl) {
function lyte_get_thumb( $thumbUrl ) {
global $lyte_thumb_error;
if (function_exists("curl_init")) {
if ( function_exists( 'curl_init' ) ) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $thumbUrl);
curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727)");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
$str = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ( !$err && $str != "" ) {
curl_setopt( $curl, CURLOPT_URL, $thumbUrl );
curl_setopt( $curl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727)');
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $curl, CURLOPT_CONNECTTIMEOUT, 5 );
$str = curl_exec( $curl );
$err = curl_error( $curl );
curl_close( $curl );
if ( ! $err && $str != '' ) {
return $str;
} else {
$lyte_thumb_error .= "curl err: ".$err."/ ";
$lyte_thumb_error .= 'curl err: ' . $err . '/ ';
}
} else {
$lyte_thumb_error .= "no curl/ ";
$lyte_thumb_error .= 'no curl/ ';
}

// if no curl or if curl error
$resp = file_get_contents($thumbUrl);
// if no curl or if curl error.
$resp = file_get_contents( $thumbUrl );
return $resp;
}

function get_origThumbURL() {
$invalid = false;

// get thumbnail-url from request
if ( array_key_exists( 'origThumbUrl', $_GET ) && $_GET['origThumbUrl'] !== '' ) {
$origThumbURL = urldecode( $_GET['origThumbUrl'] );
} else {
$invalid = true;
}

// break URL in parts to investigate.
$origThumbURL_parts = parse_url( $origThumbURL );

// make sure the thumbnail-domain is for youtube.
$origThumbDomain = $origThumbURL_parts['host'];
if ( ! $invalid && str_replace( array( 'ytimg.com','youtube.com','youtu.be' ), '', $origThumbDomain ) === $origThumbDomain ) {
$invalid = true;
}

// and make sure the thumbnail-url is for an image (.jpg)
$origThumbPath = $origThumbURL_parts['path'];
if ( ! $invalid && strpos( $origThumbPath, '.jpg' ) !== strlen( $origThumbPath ) - 4 ) {
$invalid = true;
}

// one of the above checks was not OK, so replace with fallback thumb URL (grey background).
if ( $invalid ) {
$origThumbURL = 'https://i.ytimg.com/vi/thisisnotavalidvid/hqdefault.jpg';
}

return $origThumbURL;
}

function lyte_thumb_fallback() {
global $origThumbURL, $lyte_thumb_error, $lyte_thumb_report_err;
// if for any reason we can't show a local thumbnail, we redirect to the original one
if ( strpos( $origThumbURL, "http" ) !== 0) {
$origThumbURL = "https:".$origThumbURL;
if ( strpos( $origThumbURL, 'http' ) !== 0) {
$origThumbURL = 'https:' . $origThumbURL;
}
if ( $lyte_thumb_report_err ) {
header('X-lyte-error: '.$lyte_thumb_error);
Expand Down
36 changes: 35 additions & 1 deletion wp-youtube-lyte.php
Original file line number Diff line number Diff line change
Expand Up @@ -615,10 +615,17 @@ function lyte_init() {
$mobJS = "var mOs=navigator.userAgent.match(/(iphone|ipad|ipod|android)/i);";
}

// if we're caching local thumbnails and filter says so, create lyteCookie cookie to prevent image hotlinking.
if ( get_option( 'lyte_local_thumb', '0' ) === '1' && apply_filters( 'lyte_filter_local_thumb_doublecheck', false ) ) {
$doublecheck_thumb_cookie = 'document.cookie="lyteCookie=1;path=/;samesite=strict";';
} else {
$doublecheck_thumb_cookie = '';
}

/** API: filter hook to change css */
$lyte_css = apply_filters( 'lyte_css', $lyte_css);

echo "<script type=\"text/javascript\">var bU='".$lyteSettings['path']."';".$mobJS."style = document.createElement('style');style.type = 'text/css';rules = document.createTextNode(\"".$lyte_css."\" );if(style.styleSheet) { style.styleSheet.cssText = rules.nodeValue;} else {style.appendChild(rules);}document.getElementsByTagName('head')[0].appendChild(style);</script>";
echo "<script type=\"text/javascript\">var bU='" . $lyteSettings['path'] . "';" . $mobJS . $doublecheck_thumb_cookie . "style = document.createElement('style');style.type = 'text/css';rules = document.createTextNode(\"".$lyte_css."\" );if(style.styleSheet) { style.styleSheet.cssText = rules.nodeValue;} else {style.appendChild(rules);}document.getElementsByTagName('head')[0].appendChild(style);</script>";
echo "<script type=\"text/javascript\" async src=\"".$lyteSettings['path'].$lyteSettings['file']."\"></script>";
}

Expand Down Expand Up @@ -798,17 +805,44 @@ function lyte_prepare( $the_content ) {
return $the_content;
}

function lytecache_doublecheck_activator() {
// image hotlinking protection: conditionally (by filter, off by default)
// create a file telling lyteCache to check for a cookie before serving thumbnail.
// file is also set if local thumbnail caching is not active, rendering lyteCache harmless.
if ( ! defined( 'LYTE_CACHE_DIR' ) ) {
define( 'LYTE_CACHE_DIR', WP_CONTENT_DIR .'/cache/lyteCache' );
}
$_doublecheck_activator_file = LYTE_CACHE_DIR . '/doubleCheckLyteThumbnailCache.txt';

if ( ( get_option( 'lyte_local_thumb', '0' ) === '1' && apply_filters( 'lyte_filter_local_thumb_doublecheck', false ) ) || get_option( 'lyte_local_thumb', '0' ) !== '1' ) {
if ( ! file_exists( $_doublecheck_activator_file ) ) {
if ( ! file_exists( LYTE_CACHE_DIR ) ) {
// create LYTE cache dir (and index.html) if it doesn't exist yet.
@mkdir( LYTE_CACHE_DIR, 0775, true );
@file_put_contents( LYTE_CACHE_DIR . '/index.html', '<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/wp-youtube-lyte/" rel="nofollow">WP YouTube Lyte</a></body></html>' );
}
// file needed but not found, create it.
@file_put_contents( $_doublecheck_activator_file, 'This file is used to ensure lyteCache.php is not abused (prevent hotlinking of cached YouTube thumbnails or lyteCache.php being accessed when local thumbnail caching is not active).' );
}
} elseif ( file_exists( $_doublecheck_activator_file ) ) {
// file exists but not needed (any more), delete it.
@unlink( $_doublecheck_activator_file );
}
}

/** hooking it all up to wordpress */
if ( is_admin() ) {
require_once(dirname(__FILE__).'/options.php');
add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), 'lyte_add_action_link' );
add_action( 'admin_init', 'lytecache_doublecheck_activator' );
} else {
add_filter('the_content', 'lyte_prepare', 4);
add_filter('the_content', 'lyte_parse', 10);
add_shortcode("lyte", "shortcode_lyte");
remove_filter('get_the_excerpt', 'wp_trim_excerpt');
add_filter('get_the_excerpt', 'lyte_trim_excerpt');
add_action('schedule_captions_lookup', 'captions_lookup', 1, 3);
add_action( 'init', 'lytecache_doublecheck_activator' );

/** API: action hook to allow extra actions or filters to be added */
do_action("lyte_actionsfilters");
Expand Down

0 comments on commit f38e4d1

Please sign in to comment.