403 ) ); } $age = time() - (int) $parts[0]; if ( $age > self::LIFETIME ) { return new \WP_Error( 'primiko_token_expired', __( 'This form has expired. Please try again.', 'primiko' ), array( 'status' => 403 ) ); } if ( $age < self::min_fill_time() ) { return new \WP_Error( 'primiko_too_fast', __( 'Your request could not be sent. Please try again in a moment.', 'primiko' ), array( 'status' => 400 ) ); } if ( get_transient( self::used_key( $parts[2] ) ) ) { return new \WP_Error( 'primiko_token_used', __( 'This request has already been sent.', 'primiko' ), array( 'status' => 409 ) ); } return true; } /** * Claims a token for the submission being processed. add_option() is * atomic in the database: of two parallel requests with the same token, * only one gets it. * * @param string $token Token checked with verify(). * @return bool Whether this request got the token. */ public static function claim( $token ) { return add_option( self::claim_key( $token ), time(), '', false ); } /** * Gives a claimed token back, after a refusal the visitor can fix. * * @param string $token Claimed token. */ public static function release( $token ) { delete_option( self::claim_key( $token ) ); } /** * Option key that claims a token. * * @param string $token Token. * @return string */ private static function claim_key( $token ) { $parts = explode( '.', (string) $token ); return 'primiko_claim_' . substr( (string) end( $parts ), 0, 32 ); } /** * Marks a token as used, so that the same request cannot be saved twice, * and frees its claim. * * @param string $token Token checked with verify(). */ public static function consume( $token ) { $parts = explode( '.', (string) $token ); set_transient( self::used_key( end( $parts ) ), 1, self::LIFETIME ); self::release( $token ); } /** * Transient key that remembers a used token. * * @param string $signature Signature part of the token. * @return string */ private static function used_key( $signature ) { return 'primiko_token_' . substr( $signature, 0, 32 ); } /** * Signs the parts of a token with the site salts. * * @param int $form_id Form post ID. * @param int $time Time the token was issued. * @param string $salt Random part. * @return string */ private static function sign( $form_id, $time, $salt ) { return hash_hmac( 'sha256', 'primiko|' . (int) $form_id . '|' . $time . '|' . $salt, wp_salt( 'nonce' ) ); } /** * Minimum time, in seconds, between the display of the form and its * submission. The script waits for it before sending. * * @return int */ public static function min_fill_time() { /** * Filters the minimum time, in seconds, between the display of the form * and its submission. Robots usually send forms at once. * * @param int $seconds Minimum filling time. */ return max( 0, (int) apply_filters( 'primiko_min_fill_time', 5 ) ); } }