$data['wp_signature_b64'] = $this->_sign_b64( $data['wp_ts'] );
'site_url' => site_url(),
'ref' => $this->_get_ref_url(),
wp_safe_redirect( $this->_cloud_server_dash . '/' . self::SVC_U_ENABLE_CDN . '?data=' . rawurlencode( Utility::arr2str( $param ) ) );
* Encrypt data for cloud req
* @param string|int $data Data to sign.
private function _sign_b64( $data ) {
if ( empty( $this->_summary['sk_b64'] ) ) {
self::debugErr( 'No sk to sign.' );
$sk = base64_decode( $this->_summary['sk_b64'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
if ( strlen( $sk ) !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES ) {
self::debugErr( 'Invalid local sign sk length.' );
unset( $this->_summary['pk_b64'] );
unset( $this->_summary['sk_b64'] );
self::debug( 'Clear local sign pk/sk pair.' );
$signature = sodium_crypto_sign_detached( (string) $data, $sk );
return base64_encode( $signature ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
* Load server pk from cloud
* @param bool $from_wpapi Load from WP API server.
* @return string|false Binary public key or false.
private function _load_server_pk( $from_wpapi = false ) {
$server_key_url = $this->_cloud_server . '/' . self::API_SERVER_KEY_SIGN;
$server_key_url = $this->_cloud_server_wp . '/' . self::API_SERVER_KEY_SIGN;
$resp = wp_safe_remote_get( $server_key_url );
if ( is_wp_error( $resp ) ) {
self::debugErr( 'Failed to load key: ' . $resp->get_error_message() );
$pk = trim( $resp['body'] );
self::debug( 'Loaded key from ' . $server_key_url . ': ' . $pk );
$cloud_pk = base64_decode( $pk ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
if ( strlen( $cloud_pk ) !== SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES ) {
self::debugErr( 'Invalid cloud public key length.' );
$sk = base64_decode( $this->_summary['sk_b64'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
if ( strlen( $sk ) !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES ) {
self::debugErr( 'Invalid local secret key length.' );
unset( $this->_summary['pk_b64'] );
unset( $this->_summary['sk_b64'] );
self::debug( 'Unset local pk/sk pair.' );
* WPAPI echo back to notify the sealed databox
public function wp_rest_echo() {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
self::debug( 'Parsing echo', $_POST );
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$ts = !empty( $_POST['wpapi_ts'] ) ? sanitize_text_field( wp_unslash( $_POST['wpapi_ts'] ) ) : '';
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$sig = !empty( $_POST['wpapi_signature_b64'] ) ? sanitize_text_field( wp_unslash( $_POST['wpapi_signature_b64'] ) ) : '';
if ( empty( $ts ) || empty( $sig ) ) {
return self::err( 'No echo data' );
$is_valid = $this->_validate_signature( $sig, $ts, true );
return self::err( 'Data validation from WPAPI REST Echo failed' );
$diff = time() - (int) $ts;
if ( abs( $diff ) > 86400 ) {
self::debugErr( 'WPAPI echo data timeout [diff] ' . $diff );
return self::err( 'Echo data expired' );
$signature_b64 = $this->_sign_b64( $ts );
self::debug( 'Response to echo [signature_b64] ' . $signature_b64 );
return self::ok( [ 'signature_b64' => $signature_b64 ] );
* @param string $signature_b64 Base64 signature.
* @param string $data Data to validate.
* @param bool $from_wpapi Whether the signature is from WP API server.
private function _validate_signature( $signature_b64, $data, $from_wpapi = false ) {
$cloud_pk = $this->_load_server_pk( $from_wpapi );
$signature = base64_decode( $signature_b64 ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
$is_valid = sodium_crypto_sign_verify_detached( $signature, (string) $data, $cloud_pk );
} catch ( \SodiumException $e ) {
self::debugErr( 'Decryption failed: ' . esc_html( $e->getMessage() ) );
self::debug( 'Signature validation result: ' . ( $is_valid ? 'true' : 'false' ) );
* Finish qc activation after redirection back from QC
* @param string|false $ref Ref slug.
public function finish_qc_activation( $ref = false ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
$qc_activated = !empty( $_GET['qc_activated'] ) ? sanitize_text_field( wp_unslash( $_GET['qc_activated'] ) ) : '';
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
$qc_ts = !empty( $_GET['qc_ts'] ) ? sanitize_text_field( wp_unslash( $_GET['qc_ts'] ) ) : '';
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
$qc_sig = !empty( $_GET['qc_signature_b64'] ) ? sanitize_text_field( wp_unslash( $_GET['qc_signature_b64'] ) ) : '';
if ( ! $qc_activated || ! $qc_ts || ! $qc_sig ) {
$data_to_validate_signature = [
'wp_pk_b64' => $this->_summary['pk_b64'],
$is_valid = $this->_validate_signature( $qc_sig, implode( '', $data_to_validate_signature ) );
self::debugErr( 'Failed to validate qc activation data' );
Admin_Display::error( sprintf( __( 'Failed to validate %s activation data.', 'litespeed-cache' ), 'QUIC.cloud' ) );
self::debug( 'QC activation status: ' . $qc_activated );
if ( ! in_array( $qc_activated, [ 'anonymous', 'linked', 'cdn' ], true ) ) {
self::debugErr( 'Failed to parse qc activation status' );
Admin_Display::error( sprintf( __( 'Failed to parse %s activation status.', 'litespeed-cache' ), 'QUIC.cloud' ) );
$diff = time() - (int) $qc_ts;
if ( abs( $diff ) > 86400 ) {
self::debugErr( 'QC activation data timeout [diff] ' . $diff );
Admin_Display::error( sprintf( __( '%s activation data expired.', 'litespeed-cache' ), 'QUIC.cloud' ) );
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
$main_domain = ! empty( $_GET['main_domain'] ) ? sanitize_text_field( wp_unslash( $_GET['main_domain'] ) ) : false;
$this->update_qc_activation( $qc_activated, $main_domain );
wp_safe_redirect( $this->_get_ref_url( $ref ) );
* Finish qc activation process
* @param string $qc_activated Activation status.
* @param string|bool $main_domain Main domain.
* @param bool $quite Quiet flag.
public function update_qc_activation( $qc_activated, $main_domain = false, $quite = false ) {
$this->_summary['qc_activated'] = $qc_activated;
$this->_summary['main_domain'] = $main_domain;
$msg = sprintf( __( 'Congratulations, %s successfully set this domain up for the anonymous online services.', 'litespeed-cache' ), 'QUIC.cloud' );
if ( 'linked' === $qc_activated ) {
$msg = sprintf( __( 'Congratulations, %s successfully set this domain up for the online services.', 'litespeed-cache' ), 'QUIC.cloud' );
// Sync possible partner info
if ( 'cdn' === $qc_activated ) {
$msg = sprintf( __( 'Congratulations, %s successfully set this domain up for the online services with CDN service.', 'litespeed-cache' ), 'QUIC.cloud' );
$this->cls( 'Conf' )->update_confs( [ self::O_CDN_QUIC => true ] );
Admin_Display::success( '🎊 ' . $msg );
$this->_clear_reset_qc_reg_msg();
* Load QC status for dash usage.
* Format to translate: `<a href="{#xxx#}" class="button button-primary">xxxx</a><a href="{#xxx#}">xxxx2</a>`
* @param string $type Type.
* @param bool $force Force refresh.
public function load_qc_status_for_dash( $type, $force = false ) {
return Str::translate_qc_apis( $this->_load_qc_status_for_dash( $type, $force ) );
* Internal: load QC status HTML for dash.
* @param string $type Type.
* @param bool $force Force refresh.
private function _load_qc_status_for_dash( $type, $force = false ) {
! empty( $this->_summary['mini_html'] ) &&
isset( $this->_summary['mini_html'][ $type ] ) &&
! empty( $this->_summary['mini_html'][ 'ttl.' . $type ] ) &&
$this->_summary['mini_html'][ 'ttl.' . $type ] > time()
return Str::safe_html( $this->_summary['mini_html'][ $type ] );
// Try to update dash content
$data = self::post( self::SVC_D_DASH, [ 'action2' => ( 'cdn_dash_mini' === $type ? 'cdn_dash' : $type ) ] );
if ( ! empty( $data['qc_activated'] ) ) {
if ( empty( $this->_summary['qc_activated'] ) || $this->_summary['qc_activated'] !== $data['qc_activated'] ) {
$msg = sprintf( __( 'Congratulations, %s successfully set this domain up for the online services with CDN service.', 'litespeed-cache' ), 'QUIC.cloud' );
Admin_Display::success( '🎊 ' . $msg );
$this->_clear_reset_qc_reg_msg();
$this->cls( 'Conf' )->update_confs( [ self::O_CDN_QUIC => true ] );
$this->cls( 'CDN\Quic' )->try_sync_conf( true );
$this->_summary['qc_activated'] = $data['qc_activated'];
if ( isset( $this->_summary['mini_html'][ $type ] ) ) {
return Str::safe_html( $this->_summary['mini_html'][ $type ] );
public function update_cdn_status() {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$qc_activated = !empty( $_POST['qc_activated'] ) ? sanitize_text_field( wp_unslash( $_POST['qc_activated'] ) ) : '';
if ( !$qc_activated || ! in_array( $qc_activated, [ 'anonymous', 'linked', 'cdn', 'deleted' ], true ) ) {
return self::err( 'lack_of_params' );
self::debug( 'update_cdn_status request hash: ' . $qc_activated );
if ( 'deleted' === $qc_activated ) {
$this->_summary['qc_activated'] = $qc_activated;
if ( 'cdn' === $qc_activated ) {
$msg = sprintf( __( 'Congratulations, %s successfully set this domain up for the online services with CDN service.', 'litespeed-cache' ), 'QUIC.cloud' );
Admin_Display::success( '🎊 ' . $msg );
$this->_clear_reset_qc_reg_msg();
$this->cls( 'Conf' )->update_confs( [ self::O_CDN_QUIC => true ] );
$this->cls( 'CDN\Quic' )->try_sync_conf( true );
return self::ok( [ 'qc_activated' => $qc_activated ] );
public function reset_qc() {
unset( $this->_summary['pk_b64'] );
unset( $this->_summary['sk_b64'] );
unset( $this->_summary['qc_activated'] );
if ( ! empty( $this->_summary['partner'] ) ) {
unset( $this->_summary['partner'] );
self::debug( 'Clear local QC activation.' );
Admin_Display::success( sprintf( __( 'Reset %s activation successfully.', 'litespeed-cache' ), 'QUIC.cloud' ) );
wp_safe_redirect( $this->_get_ref_url() );
* Show latest commit version always if is on dev
public function check_dev_version() {
if ( ! preg_match( '/[^\d\.]/', Core::VER ) ) {
$last_check = empty( $this->_summary[ 'last_request.' . self::API_VER ] ) ? 0 : $this->_summary[ 'last_request.' . self::API_VER ];
if ( time() - $last_check > 86400 ) {
$auto_v = self::version_check( 'dev' );
if ( ! empty( $auto_v['dev'] ) ) {
self::save_summary( [ 'version.dev' => $auto_v['dev'] ] );
if ( empty( $this->_summary['version.dev'] ) ) {
self::debug( 'Latest dev version ' . $this->_summary['version.dev'] );
if ( version_compare( $this->_summary['version.dev'], Core::VER, '<=' ) ) {
require_once LSCWP_DIR . 'tpl/banner/new_version_dev.tpl.php';
* @param string|false $src Source.
public static function version_check( $src = false ) {
'v' => defined( 'LSCWP_CUR_V' ) ? LSCWP_CUR_V : '',
// If code ver is smaller than db ver, bypass
if ( ! empty( $req_data['v'] ) && version_compare( Core::VER, $req_data['v'], '<' ) ) {
if ( defined( 'LITESPEED_ERR' ) ) {
$litespeed_err = constant( 'LITESPEED_ERR' );
$req_data['err'] = base64_encode( ! is_string( $litespeed_err ) ? wp_json_encode( $litespeed_err ) : $litespeed_err ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
$data = self::post( self::API_VER, $req_data );
if ( empty( $this->_summary['news.new'] ) ) {
if ( ! empty( $this->_summary['news.plugin'] ) && Activation::cls()->dash_notifier_is_plugin_active( $this->_summary['news.plugin'] ) ) {
require_once LSCWP_DIR . 'tpl/banner/cloud_news.tpl.php';
private function _update_news() {
if ( ! empty( $this->_summary['news.utime'] ) && time() - (int) $this->_summary['news.utime'] < 86400 * 7 ) {
self::save_summary( [ 'news.utime' => time() ] );
$data = self::get( self::API_NEWS );
if ( empty( $data['id'] ) ) {
if ( ! empty( $this->_summary['news.id'] ) && (string) $this->_summary['news.id'] === (string) $data['id'] ) {
$this->_summary['news.id'] = $data['id'];
$this->_summary['news.plugin'] = ! empty( $data['plugin'] ) ? $data['plugin'] : '';
$this->_summary['news.title'] = ! empty( $data['title'] ) ? $data['title'] : '';
$this->_summary['news.content'] = ! empty( $data['content'] ) ? $data['content'] : '';
$this->_summary['news.zip'] = ! empty( $data['zip'] ) ? $data['zip'] : '';
$this->_summary['news.new'] = 1;
if ( $this->_summary['news.plugin'] ) {
$plugin_info = Activation::cls()->dash_notifier_get_plugin_info( $this->_summary['news.plugin'] );
if ( $plugin_info && ! empty( $plugin_info->name ) ) {
$this->_summary['news.plugin_name'] = $plugin_info->name;
* Check if contains a package in a service or not
* @param string $service Service.
* @param int $pkg Package flag.
public function has_pkg( $service, $pkg ) {
if ( ! empty( $this->_summary[ 'usage.' . $service ]['pkgs'] ) && ( $this->_summary[ 'usage.' . $service ]['pkgs'] & $pkg ) ) {
* Get allowance of current service
* @param string $service Service.
* @param string|bool $err Error code by ref.
public function allowance( $service, &$err = false ) {
// Only auto sync usage at most one time per day
if ( empty( $this->_summary[ 'last_request.' . self::SVC_D_USAGE ] ) || time() - (int) $this->_summary[ 'last_request.' . self::SVC_D_USAGE ] > 86400 ) {