PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 15.0
Jetpack – WP Security, Backup, Speed, & Growth v15.0
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 All 500 releases
jetpack / unauth-file-upload.php
unauth-file-upload.php
163 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Unauthenticated File Upload Helper Functions.
4 *
5 * @package automattic/jetpack
6 */
7
8 namespace Automattic\Jetpack\UnauthFileUpload;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit( 0 );
12 }
13
14 add_action( 'wp_ajax_jetpack_unauth_file_download', __NAMESPACE__ . '\handle_file_download' );
15 add_filter( 'jetpack_unauth_file_upload_get_file', __NAMESPACE__ . '\get_file_content', 10, 2 );
16 add_filter( 'jetpack_unauth_file_download_url', __NAMESPACE__ . '\filter_get_download_url', 10, 2 );
17
18 /**
19 * Get the file download URL filter callback.
20 *
21 * @param string $url The file download URL.
22 * @param int $file_id The file ID.
23 *
24 * @return string The file download URL.
25 */
26 function filter_get_download_url( $url, $file_id ) {
27 $nonce = wp_create_nonce( 'jetpack_unauth_file_download_nonce_' . $file_id );
28 return add_query_arg(
29 array(
30 'action' => 'jetpack_unauth_file_download',
31 'file_id' => $file_id,
32 '_wpnonce' => $nonce,
33 ),
34 admin_url( 'admin-ajax.php' )
35 );
36 }
37
38 /**
39 * Handle file download requests from the admin page.
40 *
41 * @return never This method never returns as it exits directly
42 */
43 function handle_file_download() {
44 if ( ! current_user_can( 'edit_pages' ) ) {
45 wp_die( esc_html__( 'Sorry, you are not allowed to access this page.', 'jetpack' ) );
46 }
47
48 $file_id = isset( $_GET['file_id'] ) ? absint( wp_unslash( $_GET['file_id'] ) ) : 0;
49
50 if ( ! $file_id ) {
51 wp_die( esc_html__( 'Invalid file request.', 'jetpack' ) );
52 }
53
54 if (
55 ! isset( $_GET['_wpnonce'] ) ||
56 ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'jetpack_unauth_file_download_nonce_' . $file_id ) ) {
57 wp_die( esc_html__( 'Invalid nonce.', 'jetpack' ) );
58 }
59
60 /**
61 * Get the file content that we send to the user to download.
62 *
63 * @since 14.6
64 *
65 * @param array $file_content The file content.
66 * @param string $file_id The file ID.
67 *
68 * @return array|\WP_Error The file array, containing the content, name and type.
69 */
70 $file = apply_filters( 'jetpack_unauth_file_upload_get_file', array(), $file_id );
71
72 if ( is_wp_error( $file ) || empty( $file ) ) {
73 wp_die( esc_html__( 'Error retrieving file content.', 'jetpack' ) );
74 }
75
76 $is_preview = isset( $_GET['preview'] ) && 'true' === $_GET['preview'];
77
78 // Clean output buffer
79 if ( ob_get_length() ) {
80 ob_clean();
81 }
82 // Set headers for download
83 header( 'Content-Type: ' . $file['type'] );
84
85 if ( ! $is_preview ) {
86 // Forcing the file to be downloaded is important to prevent XSS attacks.
87 header( 'Content-Disposition: attachment; filename="' . sanitize_file_name( $file['name'] ) . '"' );
88 } else {
89 // For preview mode, use inline disposition
90 header( 'Content-Disposition: inline; filename="' . sanitize_file_name( $file['name'] ) . '"' );
91 }
92 header( 'Content-Length: ' . strlen( $file['content'] ) );
93 header( 'Content-Transfer-Encoding: binary' );
94 header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
95 header( 'Pragma: no-cache' );
96 header( 'Expires: 0' );
97
98 // Output file content and exit
99 echo $file['content']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Binary file data
100 exit( 0 );
101 }
102
103 /**
104 * Get the file content.
105 *
106 * @param array $file_content The file content, name and type.
107 * @param integer $file_id The file ID.
108 * @return array|\WP_Error The file content, name and type
109 */
110 function get_file_content( $file_content, $file_id ) {
111 if ( ( new \Automattic\Jetpack\Status\Host() )->is_wpcom_simple() ) {
112 return $file_content;
113 }
114
115 $blog_id = \Jetpack_Options::get_option( 'id' );
116 $request_url = sprintf( '/sites/%d/unauth-file-upload/%s', $blog_id, $file_id );
117
118 $response = \Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_blog(
119 $request_url,
120 'v2',
121 array(
122 'method' => 'GET',
123 ),
124 null,
125 'wpcom'
126 );
127
128 $file_content = wp_remote_retrieve_body( $response );
129
130 if ( is_wp_error( $response ) || empty( $file_content ) ) {
131 return new \WP_Error( 'jetpack_unauth_file_upload_error', esc_html__( 'Error retrieving file content.', 'jetpack' ) );
132 }
133
134 try {
135 $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
136 if ( isset( $content['message'] ) ) {
137 return new \WP_Error( 'jetpack_unauth_file_upload_error', esc_html__( 'Error retrieving file content.', 'jetpack' ) );
138 }
139 } catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
140 // If the file is not JSON, we assume it's a binary file.
141 }
142
143 $content_disposition = wp_remote_retrieve_header( $response, 'content-disposition' );
144 $filename = '';
145 if ( $content_disposition ) {
146 // Match the filename using a regular expression
147 if ( preg_match( '/filename="([^"]+)"/', $content_disposition, $matches ) ) {
148 $filename = $matches[1]; // Extract the filename
149 }
150 }
151
152 $type = wp_remote_retrieve_header( $response, 'content-type' );
153 if ( empty( $type ) ) {
154 $type = 'application/octet-stream'; // Default to binary if no content type is found
155 }
156
157 return array(
158 'content' => $file_content,
159 'type' => $type,
160 'name' => $filename,
161 );
162 }
163