@@ -57,8 +57,12 @@ public function render( $file, $view_dir = null ) {
5757 $ {$ key } = $ value ;
5858 }
5959
60- $ view_dir = isset ( $ view_dir ) ? trailingslashit ( $ view_dir ) : COURIER_NOTICES_PATH . 'templates/ ' ;
61- $ view_file = $ view_dir . $ file . '.php ' ;
60+ $ view_dir = isset ( $ view_dir ) ? $ view_dir : COURIER_NOTICES_PATH . 'templates/ ' ;
61+ $ view_file = $ this ->validate_and_build_path ( $ file , $ view_dir );
62+
63+ if ( false === $ view_file ) {
64+ wp_die ( esc_html__ ( 'Invalid file path ' , 'courier-notices ' ) . ': ' . esc_html ( $ file ) );
65+ }
6266
6367 if ( ! file_exists ( $ view_file ) ) {
6468 wp_die ( esc_html ( $ view_file ) );
@@ -88,7 +92,7 @@ public function assign( $variable, $value ) {
8892 *
8993 * @since 1.0
9094 *
91- * @param string $file File to get HTML string.
95+ * @param string $file File to get HTML string.
9296 * @param string $view_dir View directory.
9397 *
9498 * @return string $html HTML output as string
@@ -99,7 +103,11 @@ public function get_text_view( $file, $view_dir = null ) {
99103 }
100104
101105 $ view_dir = isset ( $ view_dir ) ? $ view_dir : COURIER_NOTICES_PATH . 'templates/ ' ;
102- $ view_file = $ view_dir . $ file . '.php ' ;
106+ $ view_file = $ this ->validate_and_build_path ( $ file , $ view_dir );
107+
108+ if ( false === $ view_file ) {
109+ return '' ;
110+ }
103111
104112 if ( ! file_exists ( $ view_file ) ) {
105113 return '' ;
@@ -125,4 +133,65 @@ public function get_text_view( $file, $view_dir = null ) {
125133 protected function init_assignments () {
126134 $ this ->variables = [];
127135 }
136+
137+ /**
138+ * Validate and build secure file path
139+ *
140+ * @since 1.8.0
141+ *
142+ * @param string $file The file name/path.
143+ * @param string $view_dir The view directory.
144+ *
145+ * @return string|false The validated file path or false if invalid.
146+ */
147+ protected function validate_and_build_path ( string $ file , string $ view_dir ) {
148+ // Validate input parameters.
149+ if ( ! is_string ( $ file ) || '' === $ file ) {
150+ return false ;
151+ }
152+
153+ if ( ! is_string ( $ view_dir ) || '' === $ view_dir ) {
154+ return false ;
155+ }
156+
157+ // Normalize the view directory to absolute path.
158+ $ view_dir = realpath ( $ view_dir );
159+ if ( false === $ view_dir ) {
160+ return false ;
161+ }
162+
163+ // Ensure view directory is within the plugin directory.
164+ $ plugin_path = realpath ( COURIER_NOTICES_PATH );
165+ if ( false === $ plugin_path || 0 !== strpos ( $ view_dir , $ plugin_path ) ) {
166+ return false ;
167+ }
168+
169+ // Remove any directory traversal attempts.
170+ $ file = str_replace ( [ '../ ' , '.. \\' , './ ' , '. \\' ], '' , $ file );
171+
172+ // Remove any null bytes or other dangerous characters.
173+ $ file = str_replace ( [ "\0" , "\r" , "\n" ], '' , $ file );
174+
175+ // Ensure file doesn't start with a slash or backslash.
176+ $ file = ltrim ( $ file , '/ \\' );
177+
178+ // Validate file name - allow alphanumeric, hyphens, underscores, forward slashes, and dots
179+ // but prevent directory traversal and other dangerous patterns.
180+ if ( ! preg_match ( '/^[a-zA-Z0-9\/_-]+$/ ' , $ file ) ) {
181+ return false ;
182+ }
183+
184+ // Build the full file path with .php extension, ensuring proper path separators.
185+ $ view_file = rtrim ( $ view_dir , '/ \\' ) . DIRECTORY_SEPARATOR . $ file . '.php ' ;
186+
187+ // Normalize the final path.
188+ $ real_file_path = realpath ( $ view_file );
189+
190+ // Ensure the final path is within the allowed directory.
191+ if ( false === $ real_file_path || 0 !== strpos ( $ real_file_path , $ view_dir ) ) {
192+ return false ;
193+ }
194+
195+ return $ real_file_path ;
196+ }
128197}
0 commit comments