ProShell v2.0
Dashboard
Server Info
Server: 158.106.128.192
PHP: 8.2.33
home
planet5
public_html
bansibaba.com
wp-includes
2026-09-12 12:08:07
Editing: unauth-file-upload.php
Cancel
Save Changes
<?php /** * Unauthenticated File Upload Helper Functions. * * @package automattic/jetpack */ namespace Automattic\Jetpack\UnauthFileUpload; if ( ! defined( 'ABSPATH' ) ) { exit( 0 ); } add_action( 'wp_ajax_jetpack_unauth_file_download', __NAMESPACE__ . '\handle_file_download' ); add_filter( 'jetpack_unauth_file_upload_get_file', __NAMESPACE__ . '\get_file_content', 10, 2 ); add_filter( 'jetpack_unauth_file_download_url', __NAMESPACE__ . '\filter_get_download_url', 10, 2 ); /** * The lifetime of a download link, in seconds. * * Kept generous enough that an editor returning to the responses dashboard days later still * has a working link, short enough to limit the exposure of a leaked URL. */ const DOWNLOAD_LINK_LIFETIME = 7 * DAY_IN_SECONDS; /** * The token scheme version carried in download links. * * Travels in the URL as `token_version` and is bound into the signature, so the signing scheme * can be changed in the future (bump this and branch on the value in authorize_file_download()) * without older links being mistaken for the new format. */ const DOWNLOAD_TOKEN_VERSION = 'v1'; /** * Get the secret used to sign download links. * * Defaults to the Jetpack blog token, which this feature already depends on (the file content * is fetched from WP.com as the blog). There is nothing extra to store, and reconnecting * Jetpack rotates the token and so invalidates any outstanding links. Only the secret portion * of the token (after the publicly-visible key prefix) is used, matching the rest of the * connection stack. * * @since 16.0 * * @return string The signing key, or an empty string if no secret is available. */ function get_download_signing_key() { $blog_token = ( new \Automattic\Jetpack\Connection\Tokens() )->get_access_token(); $secret = ''; if ( $blog_token && ! is_wp_error( $blog_token ) && ! empty( $blog_token->secret ) ) { // Blog tokens are stored as "key.secret"; sign with the secret part only, matching the // rest of the connection stack. Require exactly two non-empty parts so a malformed token // fails closed rather than signing links the fetch path could never honor. $parts = explode( '.', (string) $blog_token->secret, 2 ); if ( count( $parts ) === 2 && '' !== $parts[0] && '' !== $parts[1] ) { $secret = $parts[1]; } } /** * Filters the secret used to sign Forms unauthenticated file-download links. * * Defaults to the Jetpack blog token secret. Environments without a usable blog token * (e.g. WordPress.com Simple) can return their own stable, secret value here so that * download links can still be signed and verified. * * @since 16.0 * * @param string $secret The signing secret. Empty string if none is available. */ return (string) apply_filters( 'jetpack_unauth_file_download_signing_key', $secret ); } /** * Generate a signed token authorizing the download of a given file. * * Unlike a WordPress nonce, the token is not tied to a specific user or session, so a link * generated by one logged-in editor works for any logged-in editor. The expiry timestamp is * part of the signed payload, so it cannot be tampered with to extend access. * * @since 16.0 * * @param int $file_id The file ID. * @param int $expires Unix timestamp at which the link stops being valid. * @param string $version The token scheme version. Defaults to the current scheme. * * @return string The hex-encoded HMAC-SHA256 token, or an empty string if there is no signing key. */ function generate_download_token( $file_id, $expires, $version = DOWNLOAD_TOKEN_VERSION ) { $key = get_download_signing_key(); if ( '' === $key ) { return ''; } $payload = 'jetpack_unauth_file_download|' . $version . '|' . (int) $file_id . '|' . (int) $expires; return hash_hmac( 'sha256', $payload, $key ); } /** * Verify a download token against the expected signature using a constant-time comparison. * * Expiry is checked separately by the caller so it can surface a distinct "expired" message. * Fails closed when no signing key is available (i.e. the site is not connected). * * @since 16.0 * * @param int $file_id The file ID. * @param int $expires Unix timestamp the token was signed with. * @param string $token The token supplied in the request. * @param string $version The token scheme version the token was signed with. * * @return bool True if the token matches. */ function verify_download_token( $file_id, $expires, $token, $version = DOWNLOAD_TOKEN_VERSION ) { $expected = generate_download_token( $file_id, $expires, $version ); return '' !== $expected && is_string( $token ) && '' !== $token && hash_equals( $expected, $token ); } /** * Get the file download URL filter callback. * * @param string $url The file download URL. * @param int $file_id The file ID. * * @return string The file download URL. */ function filter_get_download_url( $url, $file_id ) { $expires = time() + DOWNLOAD_LINK_LIFETIME; $token = generate_download_token( $file_id, $expires ); if ( '' === $token ) { // No signing key (disconnected site, or WP.com Simple without the signing-key filter // wired). Return the passthrough so callers omit the link instead of handing out one // that can never work, and leave a breadcrumb for operators. error_log( 'Jetpack Forms: unable to sign file download link; no signing key available.' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log return $url; } return add_query_arg( array( 'action' => 'jetpack_unauth_file_download', 'file_id' => $file_id, 'expires' => $expires, 'token_version' => DOWNLOAD_TOKEN_VERSION, 'token' => $token, ), admin_url( 'admin-ajax.php' ) ); } /** * Reject a download request with the generic invalid-link error and a debug code, then exit. * * The message stays generic so it leaks nothing to the requester and stays actionable (a token * can be rejected simply because the blog token rotated on reconnect), while the appended code * identifies which check failed, so a user-reported screenshot is enough to locate the cause: * * 1 - Missing or unrecognized token_version. * 2 - Token signature did not verify. * 3 - Legacy nonce did not verify. * 4 - No token and no legacy nonce supplied. * * @since 16.0 * * @param int $code Number identifying the failing check. * * @return void Terminates the request via wp_die(). */ function invalid_download_link( $code ) { wp_die( esc_html( sprintf( /* translators: %d: error code used for debugging. */ __( 'This download link is no longer valid. Reload the responses page to get a fresh link. (%d)', 'jetpack' ), (int) $code ) ), '', array( 'response' => 403 ) ); } /** * Authorize a download request and return the requested file ID. * * Holds the security-critical gate (capability check, then either the current signed-token * scheme or the legacy nonce fallback). Terminates the request via wp_die() on any failure and * only returns when the caller is allowed to download the file. Kept separate from the * file-serving logic in handle_file_download() so each branch is unit-testable without the * headers/exit of the serving path. * * @since 16.0 * * @return int The validated file ID. */ function authorize_file_download() { if ( ! current_user_can( 'edit_pages' ) ) { wp_die( esc_html__( 'Sorry, you are not allowed to access this page.', 'jetpack' ), '', array( 'response' => 403 ) ); } $file_id = isset( $_GET['file_id'] ) ? absint( wp_unslash( $_GET['file_id'] ) ) : 0; if ( ! $file_id ) { wp_die( esc_html__( 'Invalid file request.', 'jetpack' ), '', array( 'response' => 400 ) ); } $token = isset( $_GET['token'] ) ? sanitize_text_field( wp_unslash( $_GET['token'] ) ) : ''; if ( '' !== $token ) { // Current scheme: a site-signed, expiring token. Future schemes can branch on the version. $token_version = isset( $_GET['token_version'] ) ? sanitize_text_field( wp_unslash( $_GET['token_version'] ) ) : ''; if ( DOWNLOAD_TOKEN_VERSION !== $token_version ) { invalid_download_link( 1 ); } $expires = isset( $_GET['expires'] ) ? absint( wp_unslash( $_GET['expires'] ) ) : 0; // A link is valid up to and including its expiry second; expired once time passes it. if ( ! $expires || $expires < time() ) { wp_die( esc_html__( 'This download link has expired. Reload the responses page to get a fresh link.', 'jetpack' ), '', array( 'response' => 410 ) ); } // $token_version was asserted equal to DOWNLOAD_TOKEN_VERSION above; pass the constant so // a future multi-version branch can't accidentally feed the request value back in. if ( ! verify_download_token( $file_id, $expires, $token, DOWNLOAD_TOKEN_VERSION ) ) { invalid_download_link( 2 ); } } elseif ( isset( $_GET['_wpnonce'] ) ) { /* * Backward compatibility for legacy per-file nonce links that may still be circulating * in already-sent emails. WordPress nonces are only valid for ~24h, so these links * expire on their own shortly after the signed-token scheme ships. * * @todo Remove this legacy nonce fallback in Jetpack 16.3 or later; links signed under * the old scheme self-expire within ~24h of the signed-token scheme shipping. */ if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'jetpack_unauth_file_download_nonce_' . $file_id ) ) { invalid_download_link( 3 ); } } else { invalid_download_link( 4 ); } return $file_id; } /** * Handle file download requests from the admin page. * * @return never This method never returns as it exits directly */ function handle_file_download() { $file_id = authorize_file_download(); /** * Get the file content that we send to the user to download. * * @since 14.6 * * @param array $file_content The file content. * @param string $file_id The file ID. * * @return array|\WP_Error The file array, containing the content, name and type. */ $file = apply_filters( 'jetpack_unauth_file_upload_get_file', array(), $file_id ); if ( is_wp_error( $file ) || empty( $file ) || ! is_array( $file ) ) { wp_die( esc_html__( 'Error retrieving file content.', 'jetpack' ) ); } // Given $file can be manipulated by a filter, make sure everything is as it should be. $file['content'] ??= ''; $file['type'] ??= 'application/octet-stream'; $file['name'] ??= ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The request is already authorized in authorize_file_download() before reaching here. $is_preview = isset( $_GET['preview'] ) && 'true' === $_GET['preview'] && is_file_type_previewable( $file['type'] ); // Clean output buffer if ( ob_get_length() ) { ob_clean(); } // Set headers for download header( 'Content-Type: ' . $file['type'] ); if ( ! $is_preview ) { // Forcing the file to be downloaded is important to prevent XSS attacks. header( 'Content-Disposition: attachment; filename="' . sanitize_file_name( $file['name'] ) . '"' ); } else { // For preview mode, use inline disposition header( 'Content-Disposition: inline; filename="' . sanitize_file_name( $file['name'] ) . '"' ); } header( 'Content-Length: ' . strlen( $file['content'] ) ); header( 'Content-Transfer-Encoding: binary' ); header( 'Cache-Control: no-cache, must-revalidate, max-age=0' ); header( 'Pragma: no-cache' ); header( 'Expires: 0' ); // Output file content and exit echo $file['content']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Binary file data exit( 0 ); } /** * Get the file content. * * @param array $file_content The file content, name and type. * @param integer $file_id The file ID. * @return array|\WP_Error The file content, name and type */ function get_file_content( $file_content, $file_id ) { if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_simple() ) { return $file_content; } $blog_id = \Jetpack_Options::get_option( 'id' ); $request_url = sprintf( '/sites/%d/unauth-file-upload/%s', $blog_id, $file_id ); $response = \Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_blog( $request_url, 'v2', array( 'method' => 'GET', ), null, 'wpcom' ); $file_content = wp_remote_retrieve_body( $response ); if ( is_wp_error( $response ) || empty( $file_content ) ) { return new \WP_Error( 'jetpack_unauth_file_upload_error', esc_html__( 'Error retrieving file content.', 'jetpack' ) ); } try { $content = json_decode( $file_content, true, 3, defined( 'JSON_THROW_ON_ERROR' ) ? \JSON_THROW_ON_ERROR : 0 ); // phpcs:ignore PHPCompatibility.Constants.NewConstants.json_throw_on_errorFound if ( isset( $content['message'] ) ) { return new \WP_Error( 'jetpack_unauth_file_upload_error', esc_html__( 'Error retrieving file content.', 'jetpack' ) ); } } catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch // If the file is not JSON, we assume it's a binary file. } $content_disposition = wp_remote_retrieve_header( $response, 'content-disposition' ); $filename = ''; if ( $content_disposition ) { // Match the filename using a regular expression if ( preg_match( '/filename="([^"]+)"/', $content_disposition, $matches ) ) { $filename = $matches[1]; // Extract the filename } } $type = wp_remote_retrieve_header( $response, 'content-type' ); if ( empty( $type ) ) { $type = 'application/octet-stream'; // Default to binary if no content type is found } return array( 'content' => $file_content, 'type' => $type, 'name' => $filename, ); } /** * Check which file type is previewable in the browser without downloading them. * * Allow images with extensions jpg, jpeg, png, gif, webp and pdf files. * * @param string $file_type The MIME type of the file. * @return bool True if the file is previable, false otherwise. */ function is_file_type_previewable( $file_type ) { $previable_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf', ); return in_array( $file_type, $previable_types, true ); }
Upload Files
Cancel
Upload
Create New
Cancel
Create
Change Permissions
Cancel
Save
Change Date
Cancel
Save
Rename Item
Cancel
Save
Confirm Delete
Are you sure you want to delete the selected items?
Cancel
Delete