@@ -370,21 +370,23 @@ function es_optimizer_render_textarea_option($options, $optionName, $title, $des
370370 * @return array Validated and sanitized options
371371 */
372372function es_optimizer_validate_options ($ input ) {
373- // Security: Verify nonce for CSRF protection
374- // WordPress requires unslashing $_POST data before sanitization
375- $ nonce_value = isset ($ _POST ['es_optimizer_settings_nonce ' ]) ? wp_unslash ($ _POST ['es_optimizer_settings_nonce ' ]) : '' ;
376-
377- if (empty ($ nonce_value ) || !wp_verify_nonce ($ nonce_value , 'es_optimizer_settings_action ' )) {
378- // Add admin notice for failed nonce verification
379- add_settings_error (
380- 'es_optimizer_options ' ,
381- 'nonce_failed ' ,
382- esc_html__ ('Security verification failed. Please try again. ' , 'Simple-WP-Optimizer ' ),
383- 'error '
384- );
373+ // Security: Verify nonce for CSRF protection when using WordPress Settings API
374+ // The nonce is automatically handled by WordPress Settings API, but we add extra verification
375+ if (isset ($ _POST ['es_optimizer_settings_nonce ' ])) {
376+ $ nonceValue = sanitize_text_field (wp_unslash ($ _POST ['es_optimizer_settings_nonce ' ]));
385377
386- // Return current options without changes
387- return get_option ('es_optimizer_options ' , es_optimizer_get_default_options ());
378+ if (!wp_verify_nonce ($ nonceValue , 'es_optimizer_settings_action ' )) {
379+ // Add admin notice for failed nonce verification
380+ add_settings_error (
381+ 'es_optimizer_options ' ,
382+ 'nonce_failed ' ,
383+ esc_html__ ('Security verification failed. Please try again. ' , 'Simple-WP-Optimizer ' ),
384+ 'error '
385+ );
386+
387+ // Return current options without changes
388+ return get_option ('es_optimizer_options ' , es_optimizer_get_default_options ());
389+ }
388390 }
389391
390392 $ valid = array ();
@@ -411,82 +413,109 @@ function es_optimizer_validate_options($input) {
411413/**
412414 * Validate DNS prefetch domains with enhanced security
413415 *
414- * @param string $domains_input Raw domain input from user
416+ * @param string $domainsInput Raw domain input from user
415417 * @return string Validated and sanitized domains
416418 */
417- function es_optimizer_validate_dns_domains ($ domains_input ) {
418- $ domains = explode ("\n" , trim ($ domains_input ));
419- $ sanitized_domains = array ();
420- $ rejected_domains = array ();
419+ function es_optimizer_validate_dns_domains ($ domainsInput ) {
420+ $ domains = explode ("\n" , trim ($ domainsInput ));
421+ $ sanitizedDomains = array ();
422+ $ rejectedDomains = array ();
421423
422424 foreach ($ domains as $ domain ) {
423425 $ domain = trim ($ domain );
424426 if (empty ($ domain )) {
425427 continue ;
426428 }
427429
428- // Enhanced URL validation with security checks
429- if (!filter_var ($ domain , FILTER_VALIDATE_URL )) {
430- $ rejected_domains [] = $ domain . ' (invalid URL format) ' ;
431- continue ;
432- }
433-
434- // Use wp_parse_url instead of parse_url for WordPress compatibility
435- $ parsed_url = wp_parse_url ($ domain );
430+ $ validationResult = es_optimizer_validate_single_domain ($ domain );
436431
437- // Security: Enforce HTTPS-only domains for DNS prefetch
438- if (!isset ($ parsed_url ['scheme ' ]) || $ parsed_url ['scheme ' ] !== 'https ' ) {
439- $ rejected_domains [] = $ domain . ' (HTTPS required for security) ' ;
440- continue ;
441- }
442-
443- // Additional security checks
444- if (!isset ($ parsed_url ['host ' ])) {
445- $ rejected_domains [] = $ domain . ' (no host found) ' ;
446- continue ;
432+ if ($ validationResult ['valid ' ]) {
433+ $ sanitizedDomains [] = $ validationResult ['domain ' ];
434+ } else {
435+ $ rejectedDomains [] = $ validationResult ['error ' ];
447436 }
448-
449- $ host = $ parsed_url ['host ' ];
450-
451- // Prevent localhost and private IP ranges for security
452- $ is_local = in_array ($ host , array ('localhost ' , '127.0.0.1 ' , '::1 ' ));
453- $ is_private_ip = filter_var ($ host , FILTER_VALIDATE_IP , FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ;
454-
455- if ($ is_local || !$ is_private_ip ) {
456- $ rejected_domains [] = $ domain . ' (private/local address not allowed) ' ;
457- continue ;
458- }
459-
460- // Security: Use esc_url_raw to sanitize URLs before storing in database
461- $ sanitized_domains [] = esc_url_raw ($ domain );
462437 }
463438
464439 // Show admin notice if any domains were rejected for security reasons
465- if (!empty ($ rejected_domains )) {
466- es_optimizer_show_domain_rejection_notice ($ rejected_domains );
440+ if (!empty ($ rejectedDomains )) {
441+ es_optimizer_show_domain_rejection_notice ($ rejectedDomains );
442+ }
443+
444+ return implode ("\n" , $ sanitizedDomains );
445+ }
446+
447+ /**
448+ * Validate a single DNS prefetch domain
449+ *
450+ * @param string $domain Domain to validate
451+ * @return array Validation result with 'valid' boolean and 'domain' or 'error'
452+ */
453+ function es_optimizer_validate_single_domain ($ domain ) {
454+ // Enhanced URL validation with security checks
455+ if (!filter_var ($ domain , FILTER_VALIDATE_URL )) {
456+ return array (
457+ 'valid ' => false ,
458+ 'error ' => $ domain . ' (invalid URL format) '
459+ );
467460 }
468461
469- return implode ("\n" , $ sanitized_domains );
462+ // Use wp_parse_url instead of parse_url for WordPress compatibility
463+ $ parsedUrl = wp_parse_url ($ domain );
464+
465+ // Security: Enforce HTTPS-only domains for DNS prefetch
466+ if (!isset ($ parsedUrl ['scheme ' ]) || $ parsedUrl ['scheme ' ] !== 'https ' ) {
467+ return array (
468+ 'valid ' => false ,
469+ 'error ' => $ domain . ' (HTTPS required for security) '
470+ );
471+ }
472+
473+ // Additional security checks
474+ if (!isset ($ parsedUrl ['host ' ])) {
475+ return array (
476+ 'valid ' => false ,
477+ 'error ' => $ domain . ' (no host found) '
478+ );
479+ }
480+
481+ $ host = $ parsedUrl ['host ' ];
482+
483+ // Prevent localhost and private IP ranges for security
484+ $ isLocal = in_array ($ host , array ('localhost ' , '127.0.0.1 ' , '::1 ' ));
485+ $ isPrivateIp = filter_var ($ host , FILTER_VALIDATE_IP , FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ;
486+
487+ if ($ isLocal || !$ isPrivateIp ) {
488+ return array (
489+ 'valid ' => false ,
490+ 'error ' => $ domain . ' (private/local address not allowed) '
491+ );
492+ }
493+
494+ // Security: Use esc_url_raw to sanitize URLs before storing in database
495+ return array (
496+ 'valid ' => true ,
497+ 'domain ' => esc_url_raw ($ domain )
498+ );
470499}
471500
472501/**
473502 * Show admin notice for rejected domains
474503 *
475- * @param array $rejected_domains Array of rejected domain strings
504+ * @param array $rejectedDomains Array of rejected domain strings
476505 */
477- function es_optimizer_show_domain_rejection_notice ($ rejected_domains ) {
506+ function es_optimizer_show_domain_rejection_notice ($ rejectedDomains ) {
478507 // Security: Properly escape and limit the rejected domains in error messages
479- $ escaped_domains = array_map ('esc_html ' , array_slice ($ rejected_domains , 0 , 3 ));
480- $ rejected_message = implode (', ' , $ escaped_domains );
508+ $ escapedDomains = array_map ('esc_html ' , array_slice ($ rejectedDomains , 0 , 3 ));
509+ $ rejectedMessage = implode (', ' , $ escapedDomains );
481510
482- if (count ($ rejected_domains ) > 3 ) {
483- $ rejected_message .= esc_html__ ('... ' , 'Simple-WP-Optimizer ' );
511+ if (count ($ rejectedDomains ) > 3 ) {
512+ $ rejectedMessage .= esc_html__ ('... ' , 'Simple-WP-Optimizer ' );
484513 }
485514
486515 // translators: %s is the list of rejected domain names
487516 $ message = sprintf (
488517 esc_html__ ('Some DNS prefetch domains were rejected for security reasons: %s ' , 'Simple-WP-Optimizer ' ),
489- $ rejected_message
518+ $ rejectedMessage
490519 );
491520
492521 add_settings_error (
0 commit comments