| @@ -2826,8 +2826,89 @@ | ||
| 2826 | 2826 | return false; |
| 2827 | 2827 | } |
| 2828 | 2828 | } |
| 2829 | 2829 | |
| 2830 | +/** | |
| 2831 | + * Add a post to the guest's signed ownership cookie. | |
| 2832 | + * Called when a guest creates a new post or topic. | |
| 2833 | + * | |
| 2834 | + * @param int $postid The post ID to add to ownership | |
| 2835 | + */ | |
| 2836 | +function wpforo_add_guest_ownership( $postid ) { | |
| 2837 | + // Respect the forum cookie policy, like every other wpForo cookie writer. | |
| 2838 | + if( ! wpforo_setting( 'legal', 'cookies' ) ) return; | |
| 2839 | + | |
| 2840 | + $postid = (int) $postid; | |
| 2841 | + if( ! $postid ) return; | |
| 2842 | + | |
| 2843 | + $owned = wpforo_get_guest_owned_posts(); | |
| 2844 | + $owned[] = $postid; | |
| 2845 | + | |
| 2846 | + // Keep the newest N ids only, so the cookie can't grow past browser limits. | |
| 2847 | + $max = (int) apply_filters( 'wpforo_guest_ownership_max_posts', 100 ); | |
| 2848 | + $owned = array_values( array_unique( $owned ) ); | |
| 2849 | + if( $max > 0 && count( $owned ) > $max ) $owned = array_slice( $owned, - $max ); | |
| 2850 | + | |
| 2851 | + $data = wp_json_encode( $owned ); | |
| 2852 | + if( ! is_string( $data ) ) return; | |
| 2853 | + | |
| 2854 | + $signature = hash_hmac( 'sha256', $data, wp_salt( 'auth' ) ); | |
| 2855 | + $cookie_value = base64_encode( $data ) . '.' . $signature; | |
| 2856 | + | |
| 2857 | + $expire = time() + ( 30 * DAY_IN_SECONDS ); | |
| 2858 | + $secure = is_ssl() && 'https' === parse_url( get_option( 'home' ), PHP_URL_SCHEME ); | |
| 2859 | + $path = ( COOKIEPATH != SITECOOKIEPATH ) ? SITECOOKIEPATH : COOKIEPATH; | |
| 2860 | + | |
| 2861 | + // Suppressed: output may already have started on some themes/hooks, and a | |
| 2862 | + // failed cookie must degrade to "cannot edit", never to a PHP warning. | |
| 2863 | + @setcookie( 'wpforo_guest_ownership', $cookie_value, $expire, $path, COOKIE_DOMAIN, $secure, true ); | |
| 2864 | + | |
| 2865 | + // Make it readable within the same request. | |
| 2866 | + $_COOKIE['wpforo_guest_ownership'] = $cookie_value; | |
| 2867 | +} | |
| 2868 | + | |
| 2869 | +/** | |
| 2870 | + * Get list of post IDs this guest owns (verified via signature). | |
| 2871 | + * Returns empty array if cookie is missing, corrupted, or signature invalid. | |
| 2872 | + * | |
| 2873 | + * @return array List of post IDs | |
| 2874 | + */ | |
| 2875 | +function wpforo_get_guest_owned_posts() { | |
| 2876 | + $cookie = isset( $_COOKIE['wpforo_guest_ownership'] ) ? $_COOKIE['wpforo_guest_ownership'] : ''; | |
| 2877 | + | |
| 2878 | + // The cookie is fully attacker-controlled: it can arrive as an array | |
| 2879 | + // (wpforo_guest_ownership[]=x), which would fatal on strpos() in PHP 8. | |
| 2880 | + if( ! is_string( $cookie ) || $cookie === '' ) return []; | |
| 2881 | + | |
| 2882 | + $parts = explode( '.', $cookie, 2 ); | |
| 2883 | + if( count( $parts ) !== 2 ) return []; | |
| 2884 | + | |
| 2885 | + list( $data_b64, $signature ) = $parts; | |
| 2886 | + $data = base64_decode( $data_b64, true ); | |
| 2887 | + if( ! is_string( $data ) || $data === '' ) return []; | |
| 2888 | + | |
| 2889 | + // Verify HMAC signature - prevents forgery | |
| 2890 | + $expected = hash_hmac( 'sha256', $data, wp_salt( 'auth' ) ); | |
| 2891 | + if( ! hash_equals( $expected, $signature ) ) return []; | |
| 2892 | + | |
| 2893 | + $owned = json_decode( $data, true ); | |
| 2894 | + if( ! is_array( $owned ) ) return []; | |
| 2895 | + | |
| 2896 | + return array_values( array_filter( array_map( 'intval', $owned ) ) ); | |
| 2897 | +} | |
| 2898 | + | |
| 2899 | +/** | |
| 2900 | + * Check if the current guest owns a specific post. | |
| 2901 | + * Uses cryptographically signed cookie - cannot be forged. | |
| 2902 | + * | |
| 2903 | + * @param int $postid The post ID to check | |
| 2904 | + * @return bool True if guest owns this post | |
| 2905 | + */ | |
| 2906 | +function wpforo_guest_owns_post( $postid ) { | |
| 2907 | + $owned = wpforo_get_guest_owned_posts(); | |
| 2908 | + return in_array( (int) $postid, $owned, true ); | |
| 2909 | +} | |
| 2910 | + | |
| 2830 | 2911 | function wpforo_extra_html_parser( $extra_html = '', $allowed_html = [] ) { |
| 2831 | 2912 | if( $extra_html ) { |
| 2832 | 2913 | $extra_html = explode( ',', $extra_html ); |
| 2833 | 2914 | $extra_html = array_filter( $extra_html ); |