403 ) ); } $payload = json_decode( $payload_json, true ); if ( ! $payload || empty( $payload['user'] ) || empty( $payload['file'] ) ) { wp_die( 'Invalid token', 'Forbidden', array( 'response' => 403 ) ); } // Permission check. if ( get_current_user_id() !== intval( $payload['user'] ) ) { wp_die( 'Unauthorized', 'Forbidden', array( 'response' => 403 ) ); } // Expiry check - links older than AIMSLMS_FILE_TOKEN_TTL are rejected. if ( empty( $payload['time'] ) || ( time() - (int) $payload['time'] ) > AIMSLMS_FILE_TOKEN_TTL ) { wp_die( 'Link expired', 'Forbidden', array( 'response' => 403 ) ); } // File path - resolve relative to the uploads directory (not ABSPATH) so the // result can be verified to stay inside it, regardless of home_url()/uploads setup. $upload_dir = wp_upload_dir(); $file_path = str_replace( $upload_dir['baseurl'], $upload_dir['basedir'], $payload['file'] ); $real_path = realpath( $file_path ); $real_base = realpath( $upload_dir['basedir'] ); if ( ! $real_path || ! $real_base || 0 !== strpos( $real_path, $real_base ) ) { wp_die( 'File not found', 'Not Found', array( 'response' => 404 ) ); } $file_path = $real_path; $mime = mime_content_type( $file_path ); $size = filesize( $file_path ); $start = 0; $end = $size - 1; // Clean output buffer. if ( ob_get_level() ) { ob_end_clean(); } header( 'Content-Type: ' . $mime ); header( 'Accept-Ranges: bytes' ); /* * ====================================== * HANDLE VIDEO RANGE REQUEST. * ====================================== */ if ( isset( $_SERVER['HTTP_RANGE'] ) ) { $http_range = sanitize_text_field( wp_unslash( $_SERVER['HTTP_RANGE'] ) ); preg_match( '/bytes=(\d+)-(\d*)/', $http_range, $matches ); $start = intval( $matches[1] ); if ( ! empty( $matches[2] ) ) { $end = intval( $matches[2] ); } header( 'HTTP/1.1 206 Partial Content' ); header( "Content-Range: bytes $start-$end/$size" ); header( 'Content-Length: ' . ( $end - $start + 1 ) ); } else { header( 'Content-Length: ' . $size ); } header( 'Content-Disposition: inline; filename="' . basename( $file_path ) . '"' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen $fp = fopen( $file_path, 'rb' ); fseek( $fp, $start ); $buffer = 8192; while ( ! feof( $fp ) && ftell( $fp ) <= $end ) { if ( 0 !== connection_status() ) { break; } $bytes_left = $end - ftell( $fp ) + 1; $read_size = min( $buffer, $bytes_left ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread $chunk = fread( $fp, $read_size ); if ( false !== $chunk ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- streaming raw binary file bytes (video/PDF) for playback/download, not HTML output; running this through an HTML sanitizer like wp_kses_post() corrupts and truncates the binary stream. echo $chunk; } flush(); } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose fclose( $fp ); exit; } add_action( 'template_redirect', 'aimslms_handle_file_request' ); // PDF Viewer. add_action( 'init', function () { add_rewrite_rule( '^aimslms-secure-pdf/?', 'index.php?aimslms_secure_pdf=1', 'top' ); } ); add_filter( 'query_vars', function ( $vars ) { $vars[] = 'aimslms_secure_pdf'; return $vars; } ); add_action( 'template_redirect', function () { if ( get_query_var( 'aimslms_secure_pdf' ) ) { if ( ! session_id() ) { session_start(); } $token = ''; if ( // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Access is controlled by session token validation and user ownership. isset( $_GET['token'] ) ) { $token = sanitize_text_field( // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Access is controlled by session token validation and user ownership. wp_unslash( $_GET['token'] ) ); } if ( empty( $token ) || empty( $_SESSION['aimslms_pdf_tokens'][ $token ] ) ) { wp_die( esc_html__( 'Invalid access.', 'aims-infosoft-lms' ), '', array( 'response' => 403 ) ); } $data = array(); if ( isset( $_SESSION['aimslms_pdf_tokens'][ $token ] ) ) { $data = map_deep( wp_unslash( $_SESSION['aimslms_pdf_tokens'][ $token ] ), 'sanitize_text_field' ); } if ( time() > $data['expires'] ) { unset( $_SESSION['aimslms_pdf_tokens'][ $token ] ); wp_die( esc_html__( 'Link expired.', 'aims-infosoft-lms' ), '', array( 'response' => 403 ) ); } if ( get_current_user_id() !== (int) $data['user_id'] ) { wp_die( esc_html__( 'Unauthorized.', 'aims-infosoft-lms' ), '', array( 'response' => 403 ) ); } $pdf_url = esc_url( $data['file'] ); $lesson_title = ! empty( $data['lesson'] ) ? $data['lesson'] . ' PDF Viewer' : 'Secure PDF Viewer'; // Standalone document (no wp_head()/wp_footer()) - register/enqueue our // assets and print only these handles instead of hardcoding /