@@ -1631,15 +1644,15 @@ public static function display($message, $errfile, $errline) echo strip_tags(self::get_primed_error(ErrorHandler::$primer)) . "\n"; } - throw new ExitException("", -1); + exit(-1); // can't throw - this is the exception handler } } public static function display404($message) { - if(defined('CLI_ONLY')) + if(defined('CLI_ONLY') && CLI_ONLY) { - header("HTTP/1.0 404 Not Found"); + http_response_code(404); header("Content-type: text/plain"); } throw new ExitException("Not Found: $message", -2); From d9cbd523a9d57cac097d206a9ad5b6eecf59484d Mon Sep 17 00:00:00 2001 From: Ben XO <75862+ben-xo@users.noreply.github.com> Date: Mon, 17 Oct 2022 18:50:11 +0100 Subject: [PATCH 10/16] Typo --- dir2cast.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dir2cast.php b/dir2cast.php index 535440e..4ff8af7 100644 --- a/dir2cast.php +++ b/dir2cast.php @@ -1588,7 +1588,7 @@ public static function display($message, $errfile, $errline) if(!http_response_code()) { http_response_code(500); - }q + } } if((!defined('CLI_ONLY') || !CLI_ONLY) && !ini_get('html_errors')) From f9693c32e808db26b5a22115bd78cec380bc8001 Mon Sep 17 00:00:00 2001 From: Ben XO <75862+ben-xo@users.noreply.github.com> Date: Mon, 17 Oct 2022 21:05:45 +0100 Subject: [PATCH 11/16] Fix nasty broken links bug which depends on whether there's a / on the end of the path defines --- dir2cast.php | 87 ++++++++++++++++--------------- docker-compose/nginx/default.conf | 2 +- test/FourOhFourTest.php | 4 +- test/SettingsHandlerTest.php | 30 +++++------ test/bootstrap.php | 5 -- 5 files changed, 64 insertions(+), 64 deletions(-) diff --git a/dir2cast.php b/dir2cast.php index 4ff8af7..e2f298c 100644 --- a/dir2cast.php +++ b/dir2cast.php @@ -617,7 +617,7 @@ protected function stripBasePath($filename) { if(strlen(RSS_File_Item::$FILES_DIR) && strpos($filename, RSS_File_Item::$FILES_DIR) === 0) { - return ltrim(substr($filename, strlen(RSS_File_Item::$FILES_DIR)), '/'); + $filename = ltrim(substr($filename, strlen(RSS_File_Item::$FILES_DIR)), '/'); } return $filename; } @@ -1583,7 +1583,7 @@ public static function display($message, $errfile, $errline) { if(self::$errors) { - if(!defined('CLI_ONLY') || !CLI_ONLY) + if(!defined('CLI_ONLY')) { if(!http_response_code()) { @@ -1591,12 +1591,12 @@ public static function display($message, $errfile, $errline) } } - if((!defined('CLI_ONLY') || !CLI_ONLY) && !ini_get('html_errors')) + if((!defined('CLI_ONLY')) && !ini_get('html_errors')) { header("Content-type: text/plain"); // reset the content-type } - if((!defined('CLI_ONLY') || !CLI_ONLY ) && ini_get('html_errors')) + if((!defined('CLI_ONLY')) && ini_get('html_errors')) { header("Content-type: text/html"); // reset the content-type @@ -1650,7 +1650,7 @@ public static function display($message, $errfile, $errline) public static function display404($message) { - if(defined('CLI_ONLY') && CLI_ONLY) + if(defined('CLI_ONLY')) { http_response_code(404); header("Content-type: text/plain"); @@ -1692,18 +1692,18 @@ public static function bootstrap(array $SERVER, array $GET, array $argv) define('CLI_ONLY', true); } - if(defined('CLI_ONLY') && CLI_ONLY) { - define('DIR2CAST_BASE', realpath(dirname($argv[0]))); + if(defined('CLI_ONLY')) { + define('DIR2CAST_BASE', slashdir(realpath(dirname($argv[0])))); } else { - define('DIR2CAST_BASE', dirname(__FILE__)); + define('DIR2CAST_BASE', slashdir(dirname(__FILE__))); } // If an installation-wide config file exists, load it now. // Installation-wide config can contain TMP_DIR, MP3_DIR and MIN_CACHE_TIME. // Anything else it contains will be used as a fall-back if no dir-specific dir2cast.ini exists - if(file_exists( DIR2CAST_BASE . '/dir2cast.ini' )) + if(file_exists( DIR2CAST_BASE . 'dir2cast.ini' )) { - $ini_file_name = DIR2CAST_BASE . '/dir2cast.ini'; + $ini_file_name = DIR2CAST_BASE . 'dir2cast.ini'; self::load_from_ini( $ini_file_name ); self::finalize(array('TMP_DIR', 'MP3_BASE', 'MP3_DIR', 'MIN_CACHE_TIME', 'FORCE_PASSWORD')); define('INI_FILE', $ini_file_name); @@ -1725,11 +1725,12 @@ public static function bootstrap(array $SERVER, array $GET, array $argv) } if(!defined('MP3_DIR') && !empty($cli_options['media-dir'])) { - define('MP3_DIR', realpath($cli_options['media-dir'])); - if(!is_dir(MP3_DIR) or !is_readable(MP3_DIR)) + + if(!is_dir($cli_options['media-dir']) or !is_readable($cli_options['media-dir'])) { ErrorHandler::display404($cli_options['media-dir']); } + define('MP3_DIR', slashdir(realpath($cli_options['media-dir']))); } if(!defined('MP3_URL') && !empty($cli_options['media-url'])) { @@ -1768,13 +1769,13 @@ public static function bootstrap(array $SERVER, array $GET, array $argv) define('FORCE_PASSWORD', ''); if(!defined('TMP_DIR')) { - define('TMP_DIR', DIR2CAST_BASE . '/temp'); + define('TMP_DIR', DIR2CAST_BASE . 'temp'); } if(!defined('MP3_BASE')) { if(!empty($SERVER['HTTP_HOST'])) - define('MP3_BASE', dirname($SERVER['SCRIPT_FILENAME'])); + define('MP3_BASE', slashdir(dirname($SERVER['SCRIPT_FILENAME']))); else define('MP3_BASE', DIR2CAST_BASE); } @@ -1783,14 +1784,14 @@ public static function bootstrap(array $SERVER, array $GET, array $argv) { if(!empty($GET['dir'])) { - define('MP3_DIR', MP3_BASE . '/' . safe_path(magic_stripslashes($GET['dir']))); + define('MP3_DIR', slashdir(slashdir(MP3_BASE) . safe_path(magic_stripslashes($GET['dir'])))); if(!is_dir(MP3_DIR) or !is_readable(MP3_DIR)) { ErrorHandler::display404($GET['dir']); } } else - define('MP3_DIR', MP3_BASE); + define('MP3_DIR', slashdir(MP3_BASE)); } } @@ -1801,15 +1802,14 @@ public static function defaults(array $SERVER) { // if an MP3_DIR specific config file exists, load it now, as long as it's not the same file as the global one! if( - file_exists( MP3_DIR . '/dir2cast.ini' ) and - realpath(DIR2CAST_BASE . '/dir2cast.ini') != realpath( MP3_DIR . '/dir2cast.ini' ) + file_exists( MP3_DIR . 'dir2cast.ini' ) and + realpath(DIR2CAST_BASE . 'dir2cast.ini') != realpath( MP3_DIR . 'dir2cast.ini' ) ) { - self::load_from_ini( MP3_DIR . '/dir2cast.ini' ); + self::load_from_ini( MP3_DIR . 'dir2cast.ini' ); } self::finalize(); - if(!defined('MP3_URL')) { # This works on the principle that MP3_DIR must be under DOCUMENT_ROOT (otherwise how will you serve the MP3s?) @@ -1818,9 +1818,9 @@ public static function defaults(array $SERVER) if(!empty($SERVER['HTTP_HOST'])) { - $path_part = substr(MP3_DIR, strlen($SERVER['DOCUMENT_ROOT'])); + $path_part = substr(slashdir(MP3_DIR), strlen(slashdir($SERVER['DOCUMENT_ROOT']))); define('MP3_URL', - 'http' . (!empty($SERVER['HTTPS']) ? 's' : '') . '://' . $SERVER['HTTP_HOST'] . '/' . ltrim( rtrim( $path_part, '/' ) . '/', '/' )); + 'http' . (!empty($SERVER['HTTPS']) ? 's' : '') . '://' . $SERVER['HTTP_HOST'] . '/' . ltrim( slashdir( $path_part ), '/' )); } else define('MP3_URL', 'file://' . MP3_DIR ); @@ -1852,10 +1852,10 @@ public static function defaults(array $SERVER) if(!defined('DESCRIPTION')) { - if(file_exists(MP3_DIR . '/description.txt')) - define('DESCRIPTION', file_get_contents(MP3_DIR . '/description.txt')); - elseif(file_exists(DIR2CAST_BASE . '/description.txt')) - define('DESCRIPTION', file_get_contents(DIR2CAST_BASE . '/description.txt')); + if(file_exists(MP3_DIR . 'description.txt')) + define('DESCRIPTION', file_get_contents(MP3_DIR . 'description.txt')); + elseif(file_exists(DIR2CAST_BASE . 'description.txt')) + define('DESCRIPTION', file_get_contents(DIR2CAST_BASE . 'description.txt')); else define('DESCRIPTION', 'Podcast'); } @@ -1877,20 +1877,20 @@ public static function defaults(array $SERVER) if(!defined('ITUNES_SUBTITLE')) { - if(file_exists(MP3_DIR . '/itunes_subtitle.txt')) - define('ITUNES_SUBTITLE', file_get_contents(MP3_DIR . '/itunes_subtitle.txt')); - elseif(file_exists(DIR2CAST_BASE . '/itunes_subtitle.txt')) - define('ITUNES_SUBTITLE', file_get_contents(DIR2CAST_BASE . '/itunes_subtitle.txt')); + if(file_exists(MP3_DIR . 'itunes_subtitle.txt')) + define('ITUNES_SUBTITLE', file_get_contents(MP3_DIR . 'itunes_subtitle.txt')); + elseif(file_exists(DIR2CAST_BASE . 'itunes_subtitle.txt')) + define('ITUNES_SUBTITLE', file_get_contents(DIR2CAST_BASE . 'itunes_subtitle.txt')); else define('ITUNES_SUBTITLE', DESCRIPTION); } if(!defined('ITUNES_SUMMARY')) { - if(file_exists(MP3_DIR . '/itunes_summary.txt')) - define('ITUNES_SUMMARY', file_get_contents(MP3_DIR . '/itunes_summary.txt')); - elseif(file_exists(DIR2CAST_BASE . '/itunes_summary.txt')) - define('ITUNES_SUMMARY', file_get_contents(DIR2CAST_BASE . '/itunes_summary.txt')); + if(file_exists(MP3_DIR . 'itunes_summary.txt')) + define('ITUNES_SUMMARY', file_get_contents(MP3_DIR . 'itunes_summary.txt')); + elseif(file_exists(DIR2CAST_BASE . 'itunes_summary.txt')) + define('ITUNES_SUMMARY', file_get_contents(DIR2CAST_BASE . 'itunes_summary.txt')); else define('ITUNES_SUMMARY', DESCRIPTION); } @@ -1901,9 +1901,9 @@ public static function defaults(array $SERVER) define('IMAGE', rtrim(MP3_URL, '/') . '/image.jpg'); elseif(file_exists(rtrim(MP3_DIR, '/') . '/image.png')) define('IMAGE', rtrim(MP3_URL, '/') . '/image.png'); - elseif(file_exists(DIR2CAST_BASE . '/image.jpg')) + elseif(file_exists(DIR2CAST_BASE . 'image.jpg')) define('IMAGE', rtrim(MP3_URL, '/') . '/image.jpg'); - elseif(file_exists(DIR2CAST_BASE . '/image.png')) + elseif(file_exists(DIR2CAST_BASE . 'image.png')) define('IMAGE', rtrim(MP3_URL, '/') . '/image.png'); else define('IMAGE', ''); @@ -1915,9 +1915,9 @@ public static function defaults(array $SERVER) define('ITUNES_IMAGE', rtrim(MP3_URL, '/') . '/itunes_image.jpg'); elseif(file_exists(rtrim(MP3_DIR, '/') . '/itunes_image.png')) define('ITUNES_IMAGE', rtrim(MP3_URL, '/') . '/itunes_image.png'); - elseif(file_exists(DIR2CAST_BASE . '/itunes_image.jpg')) + elseif(file_exists(DIR2CAST_BASE . 'itunes_image.jpg')) define('ITUNES_IMAGE', rtrim(MP3_URL, '/') . '/itunes_image.jpg'); - elseif(file_exists(DIR2CAST_BASE . '/itunes_image.png')) + elseif(file_exists(DIR2CAST_BASE . 'itunes_image.png')) define('ITUNES_IMAGE', rtrim(MP3_URL, '/') . '/itunes_image.png'); else define('ITUNES_IMAGE', ''); @@ -1974,7 +1974,7 @@ public static function defaults(array $SERVER) define('CLOCK_OFFSET', 0); // Set up factory settings for Podcast subclasses - Dir_Podcast::$EMPTY_PODCAST_IS_ERROR = !defined('CLI_ONLY') || !CLI_ONLY; + Dir_Podcast::$EMPTY_PODCAST_IS_ERROR = !defined('CLI_ONLY'); Dir_Podcast::$RECURSIVE_DIRECTORY_ITERATOR = RECURSIVE_DIRECTORY_ITERATOR; Dir_Podcast::$ITEM_COUNT = ITEM_COUNT; Dir_Podcast::$MIN_FILE_AGE = MIN_FILE_AGE; @@ -2070,7 +2070,7 @@ public function update_mtime_if_metadata_files_modified() $filepath = rtrim(MP3_DIR, '/') . '/' . $file; if(!file_exists($filepath)) { - $filepath = DIR2CAST_BASE . '/' . $file; + $filepath = DIR2CAST_BASE . $file; } if(!file_exists($filepath)) { @@ -2125,7 +2125,7 @@ public function output() if(!defined('OUTPUT_FILE')) { $output = $podcast->generate(); - if(!defined('CLI_ONLY') || !CLI_ONLY) + if(!defined('CLI_ONLY')) { $podcast->http_headers(strlen($output)); } @@ -2195,6 +2195,11 @@ function utf8_for_xml($s) return preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', '', $s); } +function slashdir($dir) +{ + return rtrim($dir, '/') . '/'; +} + /* DISPATCH *********************************************/ function main($args) diff --git a/docker-compose/nginx/default.conf b/docker-compose/nginx/default.conf index a918f54..f2ab099 100644 --- a/docker-compose/nginx/default.conf +++ b/docker-compose/nginx/default.conf @@ -8,7 +8,7 @@ server { # Don't allow downloading of dir2cast.ini, as it may contain sensitive # info such as the refresh password. Also, don't allow downloading of # dir2cast.php, for security and privacy reasons. - location ~ /dir2cast\.(ini|php)$ { + location ~ \.(ini|php)$ { return 404; } diff --git a/test/FourOhFourTest.php b/test/FourOhFourTest.php index 6b36221..3f2214d 100644 --- a/test/FourOhFourTest.php +++ b/test/FourOhFourTest.php @@ -11,8 +11,8 @@ public static function setUpBeforeClass(): void public function test_non_existent_dir_prints_bare_error_CLI_case(): void { - exec('php dir2cast.php --output=out.xml --media-dir=imaginary-dir', $output, $returncode); - $this->assertEquals("Not Found: imaginary-dir", implode("\n", $output)); + exec('php dir2cast.php --media-dir=dir2cast.ini', $output, $returncode); + $this->assertEquals("Not Found: dir2cast.ini", implode("\n", $output)); $this->assertEquals(254, $returncode); // 254 is -2 } diff --git a/test/SettingsHandlerTest.php b/test/SettingsHandlerTest.php index 51da4e1..401f1d1 100644 --- a/test/SettingsHandlerTest.php +++ b/test/SettingsHandlerTest.php @@ -137,7 +137,7 @@ public function test_default_defines_set() // should not be defined as $argv was empty $this->assertFalse(defined('CLI_ONLY')); - $this->assertEquals(DIR2CAST_BASE, realpath('..')); // from bootstrap.php + $this->assertEquals(DIR2CAST_BASE, slashdir(realpath('..'))); // from bootstrap.php } /** @@ -172,7 +172,7 @@ public function test_defines_CLI_ONLY_if_argv0() $this->assertFalse(defined('CLI_ONLY')); SettingsHandler::bootstrap(array(), array(), array('dir2cast.php')); $this->assertTrue(defined('CLI_ONLY')); - $this->assertEquals(DIR2CAST_BASE, getcwd()); // from fake $argv + $this->assertEquals(DIR2CAST_BASE, slashdir(getcwd())); // from fake $argv } /** @@ -187,7 +187,7 @@ public function test_bootstrap_sets_sensible_global_defaults_for_entire_installa SettingsHandler::bootstrap(array(), array(), array($argv0, $argv1)); $this->assertEquals(MIN_CACHE_TIME, 5); $this->assertEquals(FORCE_PASSWORD, ''); - $this->assertEquals(TMP_DIR, DIR2CAST_BASE . '/temp'); + $this->assertEquals(TMP_DIR, DIR2CAST_BASE . 'temp'); $this->assertEquals(MP3_BASE, DIR2CAST_BASE); $this->assertEquals(MP3_DIR, DIR2CAST_BASE); } @@ -207,8 +207,8 @@ public function test_when_SERVER_HTTP_HOST_then_MP3_BASE_defaults_to_same_dir() /* $GET */ array(), /* $argv */ array() ); - $this->assertEquals(MP3_BASE, '/var/www'); - $this->assertEquals(MP3_DIR, '/var/www'); + $this->assertEquals(MP3_BASE, '/var/www/'); + $this->assertEquals(MP3_DIR, '/var/www/'); } /** @@ -322,8 +322,8 @@ public function test_cli_media_dir_a_ok() mkdir($this->temp_file); SettingsHandler::bootstrap(array(), array(), array("dir2cast.php", "--media-dir={$this->temp_file}")); - $this->assertEquals(MP3_BASE, realpath('.')); - $this->assertEquals(MP3_DIR, realpath($this->temp_file)); + $this->assertEquals(MP3_BASE, slashdir(realpath('.'))); + $this->assertEquals(MP3_DIR, slashdir(realpath($this->temp_file))); $this->assertFalse(http_response_code()); } @@ -338,8 +338,8 @@ public function test_GET_media_dir_a_ok() mkdir('../' . $this->temp_file); SettingsHandler::bootstrap(array(), array("dir" => $this->temp_file), array()); - $this->assertEquals(MP3_BASE, realpath('..')); // due to bootstrap.php chdir - $this->assertEquals(MP3_DIR, realpath('../' . $this->temp_file)); + $this->assertEquals(MP3_BASE, slashdir(realpath('..'))); // due to bootstrap.php chdir + $this->assertEquals(MP3_DIR, slashdir(realpath('../' . $this->temp_file))); $this->assertFalse(http_response_code()); } @@ -355,8 +355,8 @@ public function test_GET_media_dir_safe_dot_dot_1() chdir('deep/root'); SettingsHandler::bootstrap(array(), array("dir" => ".."), array()); - $this->assertEquals(MP3_BASE, realpath("{$this->starting_dir}/..")); // due to bootstrap.php chdir - $this->assertEquals(slashdir(MP3_DIR), slashdir(MP3_BASE)); + $this->assertEquals(MP3_BASE, slashdir(realpath("{$this->starting_dir}/.."))); // due to bootstrap.php chdir + $this->assertEquals(MP3_DIR, slashdir(MP3_BASE)); $this->assertFalse(http_response_code()); } @@ -372,8 +372,8 @@ public function test_GET_media_dir_safe_dot_dot_2() chdir('deep/root'); SettingsHandler::bootstrap(array(), array("dir" => "../../.."), array()); - $this->assertEquals(MP3_BASE, realpath("{$this->starting_dir}/..")); // due to bootstrap.php chdir - $this->assertEquals(slashdir(MP3_DIR), slashdir(MP3_BASE)); + $this->assertEquals(MP3_BASE, slashdir(realpath("{$this->starting_dir}/.."))); // due to bootstrap.php chdir + $this->assertEquals(MP3_DIR, slashdir(MP3_BASE)); $this->assertFalse(http_response_code()); } @@ -425,7 +425,7 @@ public function test_GET_media_dir_safe_dir_with_good_base() SettingsHandler::bootstrap(array(), array("dir" => "root"), array()); $this->assertEquals(MP3_BASE, realpath("..")); - $this->assertEquals(MP3_DIR, realpath('.')); + $this->assertEquals(MP3_DIR, realpath('.') . '/'); $this->assertFalse(http_response_code()); } @@ -528,7 +528,7 @@ public function test_CLI_ONLY_sensible_defaults() SettingsHandler::bootstrap(array(), array(), array('dir2cast.php')); SettingsHandler::defaults(array()); - $this->assertEquals(MP3_URL, 'file://' . getcwd()); + $this->assertEquals(MP3_URL, 'file://' . getcwd() . '/'); $this->assertEquals(LINK, 'http://www.example.com/'); $this->assertEquals(RSS_LINK, 'http://www.example.com/rss'); $this->assertEquals(TITLE, 'test'); // name of this folder diff --git a/test/bootstrap.php b/test/bootstrap.php index c2a1b9b..9fe6f3e 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -124,11 +124,6 @@ function fake_getopt($argv_in, $short_options, $long_options) return array(); } -function slashdir($dir) -{ - return rtrim($dir, '/') . '/'; -} - define('NO_DISPATCHER', true); require_once('../dir2cast.php'); From be9a457c3671b36c9ed77419ad7c7b0741645697 Mon Sep 17 00:00:00 2001 From: Ben XO <75862+ben-xo@users.noreply.github.com> Date: Mon, 17 Oct 2022 21:27:50 +0100 Subject: [PATCH 12/16] Make all path defines safer and avoid double slashes --- dir2cast.php | 94 ++++++++++++++++++++---------------- test/SettingsHandlerTest.php | 63 +++++++++++++++++------- 2 files changed, 98 insertions(+), 59 deletions(-) diff --git a/dir2cast.php b/dir2cast.php index e2f298c..9cb4bc3 100644 --- a/dir2cast.php +++ b/dir2cast.php @@ -1693,17 +1693,17 @@ public static function bootstrap(array $SERVER, array $GET, array $argv) } if(defined('CLI_ONLY')) { - define('DIR2CAST_BASE', slashdir(realpath(dirname($argv[0])))); + define('DIR2CAST_BASE', realpath(dirname($argv[0]))); } else { - define('DIR2CAST_BASE', slashdir(dirname(__FILE__))); + define('DIR2CAST_BASE', dirname(__FILE__)); } // If an installation-wide config file exists, load it now. // Installation-wide config can contain TMP_DIR, MP3_DIR and MIN_CACHE_TIME. // Anything else it contains will be used as a fall-back if no dir-specific dir2cast.ini exists - if(file_exists( DIR2CAST_BASE . 'dir2cast.ini' )) + if(file_exists( DIR2CAST_BASE() . 'dir2cast.ini' )) { - $ini_file_name = DIR2CAST_BASE . 'dir2cast.ini'; + $ini_file_name = DIR2CAST_BASE() . 'dir2cast.ini'; self::load_from_ini( $ini_file_name ); self::finalize(array('TMP_DIR', 'MP3_BASE', 'MP3_DIR', 'MIN_CACHE_TIME', 'FORCE_PASSWORD')); define('INI_FILE', $ini_file_name); @@ -1769,29 +1769,29 @@ public static function bootstrap(array $SERVER, array $GET, array $argv) define('FORCE_PASSWORD', ''); if(!defined('TMP_DIR')) { - define('TMP_DIR', DIR2CAST_BASE . 'temp'); + define('TMP_DIR', DIR2CAST_BASE() . 'temp'); } if(!defined('MP3_BASE')) { if(!empty($SERVER['HTTP_HOST'])) - define('MP3_BASE', slashdir(dirname($SERVER['SCRIPT_FILENAME']))); + define('MP3_BASE', dirname($SERVER['SCRIPT_FILENAME'])); else - define('MP3_BASE', DIR2CAST_BASE); + define('MP3_BASE', DIR2CAST_BASE()); } if(!defined('MP3_DIR')) { if(!empty($GET['dir'])) { - define('MP3_DIR', slashdir(slashdir(MP3_BASE) . safe_path(magic_stripslashes($GET['dir'])))); - if(!is_dir(MP3_DIR) or !is_readable(MP3_DIR)) + define('MP3_DIR', MP3_BASE() . safe_path(magic_stripslashes($GET['dir']))); + if(!is_dir(MP3_DIR()) or !is_readable(MP3_DIR())) { ErrorHandler::display404($GET['dir']); } } else - define('MP3_DIR', slashdir(MP3_BASE)); + define('MP3_DIR', MP3_BASE()); } } @@ -1802,10 +1802,10 @@ public static function defaults(array $SERVER) { // if an MP3_DIR specific config file exists, load it now, as long as it's not the same file as the global one! if( - file_exists( MP3_DIR . 'dir2cast.ini' ) and - realpath(DIR2CAST_BASE . 'dir2cast.ini') != realpath( MP3_DIR . 'dir2cast.ini' ) + file_exists( MP3_DIR() . 'dir2cast.ini' ) and + realpath(DIR2CAST_BASE() . 'dir2cast.ini') != realpath( MP3_DIR() . 'dir2cast.ini' ) ) { - self::load_from_ini( MP3_DIR . 'dir2cast.ini' ); + self::load_from_ini( MP3_DIR() . 'dir2cast.ini' ); } self::finalize(); @@ -1818,18 +1818,18 @@ public static function defaults(array $SERVER) if(!empty($SERVER['HTTP_HOST'])) { - $path_part = substr(slashdir(MP3_DIR), strlen(slashdir($SERVER['DOCUMENT_ROOT']))); + $path_part = substr(MP3_DIR(), strlen(slashdir($SERVER['DOCUMENT_ROOT']))); define('MP3_URL', 'http' . (!empty($SERVER['HTTPS']) ? 's' : '') . '://' . $SERVER['HTTP_HOST'] . '/' . ltrim( slashdir( $path_part ), '/' )); } else - define('MP3_URL', 'file://' . MP3_DIR ); + define('MP3_URL', 'file://' . MP3_DIR() ); } if(!defined('TITLE')) { - if(basename(MP3_DIR)) - define('TITLE', basename(MP3_DIR)); + if(basename(MP3_DIR())) + define('TITLE', basename(MP3_DIR())); else define('TITLE', 'My First dir2cast Podcast'); } @@ -1852,10 +1852,10 @@ public static function defaults(array $SERVER) if(!defined('DESCRIPTION')) { - if(file_exists(MP3_DIR . 'description.txt')) - define('DESCRIPTION', file_get_contents(MP3_DIR . 'description.txt')); - elseif(file_exists(DIR2CAST_BASE . 'description.txt')) - define('DESCRIPTION', file_get_contents(DIR2CAST_BASE . 'description.txt')); + if(file_exists(MP3_DIR() . 'description.txt')) + define('DESCRIPTION', file_get_contents(MP3_DIR() . 'description.txt')); + elseif(file_exists(DIR2CAST_BASE() . 'description.txt')) + define('DESCRIPTION', file_get_contents(DIR2CAST_BASE() . 'description.txt')); else define('DESCRIPTION', 'Podcast'); } @@ -1877,33 +1877,33 @@ public static function defaults(array $SERVER) if(!defined('ITUNES_SUBTITLE')) { - if(file_exists(MP3_DIR . 'itunes_subtitle.txt')) - define('ITUNES_SUBTITLE', file_get_contents(MP3_DIR . 'itunes_subtitle.txt')); - elseif(file_exists(DIR2CAST_BASE . 'itunes_subtitle.txt')) - define('ITUNES_SUBTITLE', file_get_contents(DIR2CAST_BASE . 'itunes_subtitle.txt')); + if(file_exists(MP3_DIR() . 'itunes_subtitle.txt')) + define('ITUNES_SUBTITLE', file_get_contents(MP3_DIR() . 'itunes_subtitle.txt')); + elseif(file_exists(DIR2CAST_BASE() . 'itunes_subtitle.txt')) + define('ITUNES_SUBTITLE', file_get_contents(DIR2CAST_BASE() . 'itunes_subtitle.txt')); else define('ITUNES_SUBTITLE', DESCRIPTION); } if(!defined('ITUNES_SUMMARY')) { - if(file_exists(MP3_DIR . 'itunes_summary.txt')) - define('ITUNES_SUMMARY', file_get_contents(MP3_DIR . 'itunes_summary.txt')); - elseif(file_exists(DIR2CAST_BASE . 'itunes_summary.txt')) - define('ITUNES_SUMMARY', file_get_contents(DIR2CAST_BASE . 'itunes_summary.txt')); + if(file_exists(MP3_DIR() . 'itunes_summary.txt')) + define('ITUNES_SUMMARY', file_get_contents(MP3_DIR() . 'itunes_summary.txt')); + elseif(file_exists(DIR2CAST_BASE() . 'itunes_summary.txt')) + define('ITUNES_SUMMARY', file_get_contents(DIR2CAST_BASE() . 'itunes_summary.txt')); else define('ITUNES_SUMMARY', DESCRIPTION); } if(!defined('IMAGE')) { - if(file_exists(rtrim(MP3_DIR, '/') . '/image.jpg')) + if(file_exists(MP3_DIR() . 'image.jpg')) define('IMAGE', rtrim(MP3_URL, '/') . '/image.jpg'); - elseif(file_exists(rtrim(MP3_DIR, '/') . '/image.png')) + elseif(file_exists(MP3_DIR() . 'image.png')) define('IMAGE', rtrim(MP3_URL, '/') . '/image.png'); - elseif(file_exists(DIR2CAST_BASE . 'image.jpg')) + elseif(file_exists(DIR2CAST_BASE() . 'image.jpg')) define('IMAGE', rtrim(MP3_URL, '/') . '/image.jpg'); - elseif(file_exists(DIR2CAST_BASE . 'image.png')) + elseif(file_exists(DIR2CAST_BASE() . 'image.png')) define('IMAGE', rtrim(MP3_URL, '/') . '/image.png'); else define('IMAGE', ''); @@ -1911,13 +1911,13 @@ public static function defaults(array $SERVER) if(!defined('ITUNES_IMAGE')) { - if(file_exists(rtrim(MP3_DIR, '/') . '/itunes_image.jpg')) + if(file_exists(MP3_DIR() . 'itunes_image.jpg')) define('ITUNES_IMAGE', rtrim(MP3_URL, '/') . '/itunes_image.jpg'); - elseif(file_exists(rtrim(MP3_DIR, '/') . '/itunes_image.png')) + elseif(file_exists(MP3_DIR() . 'itunes_image.png')) define('ITUNES_IMAGE', rtrim(MP3_URL, '/') . '/itunes_image.png'); - elseif(file_exists(DIR2CAST_BASE . 'itunes_image.jpg')) + elseif(file_exists(DIR2CAST_BASE() . 'itunes_image.jpg')) define('ITUNES_IMAGE', rtrim(MP3_URL, '/') . '/itunes_image.jpg'); - elseif(file_exists(DIR2CAST_BASE . 'itunes_image.png')) + elseif(file_exists(DIR2CAST_BASE() . 'itunes_image.png')) define('ITUNES_IMAGE', rtrim(MP3_URL, '/') . '/itunes_image.png'); else define('ITUNES_IMAGE', ''); @@ -1985,7 +1985,7 @@ public static function defaults(array $SERVER) // Set up up factory settings for RSS Items RSS_File_Item::$FILES_URL = MP3_URL; // TODO: rename this to MEDIA_URL - RSS_File_Item::$FILES_DIR = MP3_DIR; // TODO: rename this to MEDIA_DIR + RSS_File_Item::$FILES_DIR = MP3_DIR(); // TODO: rename this to MEDIA_DIR Media_RSS_Item::$LONG_TITLES = LONG_TITLES; Media_RSS_Item::$DESCRIPTION_SOURCE = DESCRIPTION_SOURCE; } @@ -2067,10 +2067,10 @@ public function update_mtime_if_metadata_files_modified() ); foreach($metadata_files as $file) { - $filepath = rtrim(MP3_DIR, '/') . '/' . $file; + $filepath = MP3_DIR() . $file; if(!file_exists($filepath)) { - $filepath = DIR2CAST_BASE . $file; + $filepath = DIR2CAST_BASE() . $file; } if(!file_exists($filepath)) { @@ -2200,6 +2200,18 @@ function slashdir($dir) return rtrim($dir, '/') . '/'; } +function DIR2CAST_BASE() { + return slashdir(DIR2CAST_BASE); +} + +function MP3_BASE() { + return slashdir(MP3_BASE); +} + +function MP3_DIR() { + return slashdir(MP3_DIR); +} + /* DISPATCH *********************************************/ function main($args) @@ -2214,7 +2226,7 @@ function main($args) empty($_SERVER) ? array() : $_SERVER ); - $podcast = new Locking_Cached_Dir_Podcast(MP3_DIR, TMP_DIR); + $podcast = new Locking_Cached_Dir_Podcast(MP3_DIR(), TMP_DIR); $podcast->setClockOffset(CLOCK_OFFSET); $dispatcher = new Dispatcher($podcast); diff --git a/test/SettingsHandlerTest.php b/test/SettingsHandlerTest.php index 401f1d1..9a8699e 100644 --- a/test/SettingsHandlerTest.php +++ b/test/SettingsHandlerTest.php @@ -137,7 +137,34 @@ public function test_default_defines_set() // should not be defined as $argv was empty $this->assertFalse(defined('CLI_ONLY')); - $this->assertEquals(DIR2CAST_BASE, slashdir(realpath('..'))); // from bootstrap.php + $this->assertEquals(DIR2CAST_BASE(), slashdir(realpath('..'))); // from bootstrap.php + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function test_pre_defined_slashed() + { + define('DIR2CAST_BASE', '/tmp/'); + $this->assertEquals(DIR2CAST_BASE(), '/tmp/'); + define('MP3_BASE', '/tmp/'); + $this->assertEquals(DIR2CAST_BASE(), '/tmp/'); + define('MP3_PATH', '/tmp/'); + $this->assertEquals(DIR2CAST_BASE(), '/tmp/'); + } + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function test_pre_defined_slashless() + { + define('DIR2CAST_BASE', '/tmp'); + $this->assertEquals(DIR2CAST_BASE(), '/tmp/'); + define('MP3_BASE', '/tmp'); + $this->assertEquals(DIR2CAST_BASE(), '/tmp/'); + define('MP3_PATH', '/tmp'); + $this->assertEquals(DIR2CAST_BASE(), '/tmp/'); } /** @@ -172,7 +199,7 @@ public function test_defines_CLI_ONLY_if_argv0() $this->assertFalse(defined('CLI_ONLY')); SettingsHandler::bootstrap(array(), array(), array('dir2cast.php')); $this->assertTrue(defined('CLI_ONLY')); - $this->assertEquals(DIR2CAST_BASE, slashdir(getcwd())); // from fake $argv + $this->assertEquals(DIR2CAST_BASE(), slashdir(getcwd())); // from fake $argv } /** @@ -187,9 +214,9 @@ public function test_bootstrap_sets_sensible_global_defaults_for_entire_installa SettingsHandler::bootstrap(array(), array(), array($argv0, $argv1)); $this->assertEquals(MIN_CACHE_TIME, 5); $this->assertEquals(FORCE_PASSWORD, ''); - $this->assertEquals(TMP_DIR, DIR2CAST_BASE . 'temp'); - $this->assertEquals(MP3_BASE, DIR2CAST_BASE); - $this->assertEquals(MP3_DIR, DIR2CAST_BASE); + $this->assertEquals(TMP_DIR, DIR2CAST_BASE() . 'temp'); + $this->assertEquals(MP3_BASE(), DIR2CAST_BASE()); + $this->assertEquals(MP3_DIR(), DIR2CAST_BASE()); } /** @@ -207,8 +234,8 @@ public function test_when_SERVER_HTTP_HOST_then_MP3_BASE_defaults_to_same_dir() /* $GET */ array(), /* $argv */ array() ); - $this->assertEquals(MP3_BASE, '/var/www/'); - $this->assertEquals(MP3_DIR, '/var/www/'); + $this->assertEquals(MP3_BASE(), '/var/www/'); + $this->assertEquals(MP3_DIR(), '/var/www/'); } /** @@ -322,8 +349,8 @@ public function test_cli_media_dir_a_ok() mkdir($this->temp_file); SettingsHandler::bootstrap(array(), array(), array("dir2cast.php", "--media-dir={$this->temp_file}")); - $this->assertEquals(MP3_BASE, slashdir(realpath('.'))); - $this->assertEquals(MP3_DIR, slashdir(realpath($this->temp_file))); + $this->assertEquals(MP3_BASE(), slashdir(realpath('.'))); + $this->assertEquals(MP3_DIR(), slashdir(realpath($this->temp_file))); $this->assertFalse(http_response_code()); } @@ -338,8 +365,8 @@ public function test_GET_media_dir_a_ok() mkdir('../' . $this->temp_file); SettingsHandler::bootstrap(array(), array("dir" => $this->temp_file), array()); - $this->assertEquals(MP3_BASE, slashdir(realpath('..'))); // due to bootstrap.php chdir - $this->assertEquals(MP3_DIR, slashdir(realpath('../' . $this->temp_file))); + $this->assertEquals(MP3_BASE(), slashdir(realpath('..'))); // due to bootstrap.php chdir + $this->assertEquals(MP3_DIR(), slashdir(realpath('../' . $this->temp_file))); $this->assertFalse(http_response_code()); } @@ -355,8 +382,8 @@ public function test_GET_media_dir_safe_dot_dot_1() chdir('deep/root'); SettingsHandler::bootstrap(array(), array("dir" => ".."), array()); - $this->assertEquals(MP3_BASE, slashdir(realpath("{$this->starting_dir}/.."))); // due to bootstrap.php chdir - $this->assertEquals(MP3_DIR, slashdir(MP3_BASE)); + $this->assertEquals(MP3_BASE(), slashdir(realpath("{$this->starting_dir}/.."))); // due to bootstrap.php chdir + $this->assertEquals(MP3_DIR(), MP3_BASE()); $this->assertFalse(http_response_code()); } @@ -372,8 +399,8 @@ public function test_GET_media_dir_safe_dot_dot_2() chdir('deep/root'); SettingsHandler::bootstrap(array(), array("dir" => "../../.."), array()); - $this->assertEquals(MP3_BASE, slashdir(realpath("{$this->starting_dir}/.."))); // due to bootstrap.php chdir - $this->assertEquals(MP3_DIR, slashdir(MP3_BASE)); + $this->assertEquals(MP3_BASE(), slashdir(realpath("{$this->starting_dir}/.."))); // due to bootstrap.php chdir + $this->assertEquals(MP3_DIR(), MP3_BASE()); $this->assertFalse(http_response_code()); } @@ -424,8 +451,8 @@ public function test_GET_media_dir_safe_dir_with_good_base() define('MP3_BASE', realpath('..')); SettingsHandler::bootstrap(array(), array("dir" => "root"), array()); - $this->assertEquals(MP3_BASE, realpath("..")); - $this->assertEquals(MP3_DIR, realpath('.') . '/'); + $this->assertEquals(MP3_BASE(), realpath("..") . '/'); + $this->assertEquals(MP3_DIR(), realpath('.') . '/'); $this->assertFalse(http_response_code()); } @@ -497,7 +524,7 @@ public function test_sensible_defaults($argv0) $this->assertSame(getID3_Podcast_Helper::$AUTO_SAVE_COVER_ART, AUTO_SAVE_COVER_ART); $this->assertSame(iTunes_Podcast_Helper::$ITUNES_SUBTITLE_SUFFIX, ITUNES_SUBTITLE_SUFFIX); $this->assertSame(RSS_File_Item::$FILES_URL, MP3_URL); - $this->assertSame(RSS_File_Item::$FILES_DIR, MP3_DIR); + $this->assertSame(RSS_File_Item::$FILES_DIR, MP3_DIR()); $this->assertSame(Media_RSS_Item::$LONG_TITLES, LONG_TITLES); $this->assertSame(Media_RSS_Item::$DESCRIPTION_SOURCE, DESCRIPTION_SOURCE); } From 814e5e17825271e5bc24914c8a8a4ece8e2e847d Mon Sep 17 00:00:00 2001 From: Ben XO <75862+ben-xo@users.noreply.github.com> Date: Mon, 17 Oct 2022 21:29:47 +0100 Subject: [PATCH 13/16] Update version to 1.37 before merge --- CHANGELOG.txt | 8 ++++++++ README.md | 2 +- dir2cast.php | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 48ab8ba..58d111a 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,14 @@ Changelog ========= +1.37 2022-10-17 * Errors now return an HTTP status code 500 by default. + * If the error is due to no content, or a bad URL passed to + ?dir=, then it will be a 404 and no information about + the server paths will be returned in the output. Thanks + to @EdwarDDay for this security related suggestion. + * fix nasty bug where paths were sometimes invalid due to + mishandling of trailing slashes + 1.36 2022-08-25 * Fix bug where podcasts with autosaved cover art would end up with duplicated iTunes metadata tags. Thanks once again to @EdwarDDay for the bug report. diff --git a/README.md b/README.md index 8d3cda5..b1a0d5e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Testing dir2cast](https://github.com/ben-xo/dir2cast/actions/workflows/testing.yml/badge.svg)](https://github.com/ben-xo/dir2cast/actions/workflows/testing.yml) -dir2cast by Ben XO v1.36 (2022-08-25) +dir2cast by Ben XO v1.37 (2022-10-17) ================================================================================ https://github.com/ben-xo/dir2cast/ diff --git a/dir2cast.php b/dir2cast.php index 9cb4bc3..9dfc82f 100644 --- a/dir2cast.php +++ b/dir2cast.php @@ -56,7 +56,7 @@ /* DEFAULTS *********************************************/ // error handler needs these, so let's set them now. -define('VERSION', '1.36'); +define('VERSION', '1.37'); define('DIR2CAST_HOMEPAGE', 'https://github.com/ben-xo/dir2cast/'); define('GENERATOR', 'dir2cast ' . VERSION . ' by Ben XO (' . DIR2CAST_HOMEPAGE . ')'); From 4e5044d695cd11a1289916ac3f61d255b059694d Mon Sep 17 00:00:00 2001 From: Ben XO <75862+ben-xo@users.noreply.github.com> Date: Mon, 17 Oct 2022 21:51:24 +0100 Subject: [PATCH 14/16] Some comments --- dir2cast.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dir2cast.php b/dir2cast.php index 9dfc82f..e8debf0 100644 --- a/dir2cast.php +++ b/dir2cast.php @@ -1692,6 +1692,7 @@ public static function bootstrap(array $SERVER, array $GET, array $argv) define('CLI_ONLY', true); } + // do not use DIR2CAST_BASE directly. use DIR2CAST_BASE() if(defined('CLI_ONLY')) { define('DIR2CAST_BASE', realpath(dirname($argv[0]))); } else { @@ -1730,6 +1731,7 @@ public static function bootstrap(array $SERVER, array $GET, array $argv) { ErrorHandler::display404($cli_options['media-dir']); } + // do not use MP3_DIR directly. use MP3_DIR() define('MP3_DIR', slashdir(realpath($cli_options['media-dir']))); } if(!defined('MP3_URL') && !empty($cli_options['media-url'])) @@ -1772,6 +1774,7 @@ public static function bootstrap(array $SERVER, array $GET, array $argv) define('TMP_DIR', DIR2CAST_BASE() . 'temp'); } + // do not use MP3_BASE directly. use MP3_BASE() if(!defined('MP3_BASE')) { if(!empty($SERVER['HTTP_HOST'])) @@ -1779,7 +1782,8 @@ public static function bootstrap(array $SERVER, array $GET, array $argv) else define('MP3_BASE', DIR2CAST_BASE()); } - + + // do not use MP3_DIR directly. use MP3_DIR() if(!defined('MP3_DIR')) { if(!empty($GET['dir'])) From e52ab3bf55016f627deca4ff5ddc96ade987bca8 Mon Sep 17 00:00:00 2001 From: Ben XO <75862+ben-xo@users.noreply.github.com> Date: Tue, 18 Oct 2022 16:20:46 +0100 Subject: [PATCH 15/16] Update .gitignore with fixture auto-extract images --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 1d9ec6a..243f776 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,8 @@ test/composer.lock test/vendor .unfinished +.vscode + +# using the docker-compose example will auto-extract some embedded images. ignore. +test/fixtures/id3v2_artist_album_title_cover.jpg +test/fixtures/tagged_with_cover.jpg From afd74750fec409c4c20170529c31faff07e4feed Mon Sep 17 00:00:00 2001 From: Ben XO <75862+ben-xo@users.noreply.github.com> Date: Thu, 27 Oct 2022 12:29:23 +0100 Subject: [PATCH 16/16] Update dates and changelog --- CHANGELOG.txt | 6 +++--- README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 58d111a..e0d3353 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,13 +1,13 @@ Changelog ========= -1.37 2022-10-17 * Errors now return an HTTP status code 500 by default. +1.37 2022-10-27 * Errors now return an HTTP status code 500 by default. * If the error is due to no content, or a bad URL passed to ?dir=, then it will be a 404 and no information about the server paths will be returned in the output. Thanks - to @EdwarDDay for this security related suggestion. + to @EdwarDDay for this security suggestion. (#64) * fix nasty bug where paths were sometimes invalid due to - mishandling of trailing slashes + mishandling of trailing slashes (#55) 1.36 2022-08-25 * Fix bug where podcasts with autosaved cover art would end up with duplicated iTunes metadata tags. Thanks once again to diff --git a/README.md b/README.md index b1a0d5e..5439d24 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Testing dir2cast](https://github.com/ben-xo/dir2cast/actions/workflows/testing.yml/badge.svg)](https://github.com/ben-xo/dir2cast/actions/workflows/testing.yml) -dir2cast by Ben XO v1.37 (2022-10-17) +dir2cast by Ben XO v1.37 (2022-10-27) ================================================================================ https://github.com/ben-xo/dir2cast/