Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.89% covered (warning)
88.89%
16 / 18
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
Jetpack_JSON_API_Attachment_Ownership_Trait
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
2 / 2
9
100.00% covered (success)
100.00%
1 / 1
 validate_attachment_ownership
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 validate_attachment_is_zip
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2/**
3 * Shared attachment-ownership and zip-mime validation for JSON API endpoints
4 * that consume an uploaded zip (plugin/theme install and replace).
5 *
6 * @package automattic/jetpack
7 */
8
9if ( ! defined( 'ABSPATH' ) ) {
10    exit( 0 );
11}
12
13/**
14 * Shared ownership check for endpoints that consume an uploaded attachment
15 * (plugin/theme zip uploads). Guards against an authorized caller referencing
16 * an attachment that wasn't part of this upload.
17 */
18trait Jetpack_JSON_API_Attachment_Ownership_Trait {
19
20    /**
21     * Confirm the attachment exists, is the attachment post type, and is owned
22     * by the current user when a user is identifiable. Site-auth requests
23     * (no current user) skip the author check.
24     *
25     * @param int $attachment_id Attachment post ID.
26     * @return true|WP_Error
27     */
28    protected function validate_attachment_ownership( $attachment_id ) {
29        $post = get_post( $attachment_id );
30        if ( ! $post || 'attachment' !== $post->post_type ) {
31            return new WP_Error( 'invalid_attachment', __( 'The referenced upload is not a valid attachment.', 'jetpack' ), 400 );
32        }
33
34        $current_user_id = get_current_user_id();
35        if ( $current_user_id && (int) $post->post_author !== $current_user_id ) {
36            return new WP_Error( 'attachment_not_owned', __( 'The referenced upload does not belong to the current user.', 'jetpack' ), 403 );
37        }
38
39        return true;
40    }
41
42    /**
43     * Confirm the attachment is a zip package. Reject non-zip mime types and
44     * non-.zip extensions to prevent unrelated uploads (images, PDFs, etc.)
45     * from being fed to Plugin_Upgrader / Theme_Upgrader.
46     *
47     * @param int $attachment_id Attachment post ID.
48     * @return true|WP_Error
49     */
50    protected function validate_attachment_is_zip( $attachment_id ) {
51        $mime = get_post_mime_type( $attachment_id );
52        if ( 'application/zip' !== $mime ) {
53            return new WP_Error( 'invalid_attachment_mime', __( 'Uploaded attachment is not a zip package.', 'jetpack' ), 400 );
54        }
55
56        $file = get_attached_file( $attachment_id );
57        if ( $file ) {
58            $extension = strtolower( (string) pathinfo( $file, PATHINFO_EXTENSION ) );
59            if ( 'zip' !== $extension ) {
60                return new WP_Error( 'invalid_attachment_extension', __( 'Uploaded attachment is not a .zip file.', 'jetpack' ), 400 );
61            }
62        }
63
64        return true;
65    }
66}