| @@ -65,68 +65,136 @@ | ||
| 65 | 65 | if ( ! isset( $file_parameters['file'] ) ) { |
| 66 | 66 | return new WP_Error( 'rest_missing_callback_param', 'Missing parameter(s): file', array( 'status' => 400 ) ); |
| 67 | 67 | } |
| 68 | 68 | |
| 69 | - $file = $file_parameters['file']; | |
| 69 | + $file = $file_parameters['file']; | |
| 70 | 70 | $file_settings = get_option( 'wpsc-gs-file-attachments' ); |
| 71 | - $filename = time() . '_' . sanitize_file_name( $file['name'] ); | |
| 71 | + | |
| 72 | + $original_name = sanitize_file_name( $file['name'] ); | |
| 73 | + $filename = time() . '_' . $original_name; | |
| 72 | 74 | $extension = strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) ); |
| 73 | 75 | $today = new DateTime(); |
| 74 | - $upload_dir = wp_upload_dir(); | |
| 75 | 76 | |
| 76 | - // Allowed file extension. | |
| 77 | + // Allowed extensions. | |
| 77 | 78 | $allowed_file_extensions = explode( ',', $file_settings['allowed-file-extensions'] ); |
| 78 | 79 | $allowed_file_extensions = array_map( 'trim', $allowed_file_extensions ); |
| 79 | 80 | $allowed_file_extensions = array_map( 'strtolower', $allowed_file_extensions ); |
| 80 | - if ( ! ( in_array( $extension, $allowed_file_extensions ) ) ) { | |
| 81 | + | |
| 82 | + if ( ! in_array( $extension, $allowed_file_extensions, true ) ) { | |
| 81 | 83 | wp_send_json_error( 'File extension not allowed!', 400 ); |
| 82 | 84 | } |
| 83 | 85 | |
| 84 | 86 | // Allowed file size. |
| 85 | 87 | $allowed_file_size = intval( $file_settings['attachments-max-filesize'] ) * 1000000; |
| 86 | - if ( ! ( isset( $file['size'] ) && $file['size'] <= $allowed_file_size ) ) { | |
| 88 | + if ( ! isset( $file['size'] ) || $file['size'] > $allowed_file_size ) { | |
| 87 | 89 | wp_send_json_error( 'File size exceeds allowed limit!', 400 ); |
| 88 | 90 | } |
| 89 | 91 | |
| 90 | - // Init attachment data. | |
| 92 | + // Ensure wp_handle_upload is available. | |
| 93 | + if ( ! function_exists( 'wp_handle_upload' ) ) { | |
| 94 | + require_once ABSPATH . 'wp-admin/includes/file.php'; | |
| 95 | + } | |
| 96 | + | |
| 97 | + // Validate MIME using SAME custom mime rules. | |
| 98 | + add_filter( 'upload_mimes', array( 'WPSC_Attachment', 'wpsc_custom_upload_mimes' ) ); | |
| 99 | + $wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'] ); | |
| 100 | + remove_filter( 'upload_mimes', array( 'WPSC_Attachment', 'wpsc_custom_upload_mimes' ) ); | |
| 101 | + | |
| 102 | + if ( empty( $wp_filetype['ext'] ) || empty( $wp_filetype['type'] ) ) { | |
| 103 | + wp_send_json_error( 'Invalid file type!', 400 ); | |
| 104 | + } | |
| 105 | + | |
| 106 | + // Strict extension match check. | |
| 107 | + if ( strtolower( $wp_filetype['ext'] ) !== $extension ) { | |
| 108 | + wp_send_json_error( 'File extension mismatch detected!', 400 ); | |
| 109 | + } | |
| 110 | + | |
| 111 | + if ( ! in_array( strtolower( $wp_filetype['ext'] ), $allowed_file_extensions, true ) ) { | |
| 112 | + wp_send_json_error( 'File type does not match allowed extension!', 400 ); | |
| 113 | + } | |
| 114 | + | |
| 91 | 115 | $data = array( |
| 92 | - 'name' => sanitize_file_name( $file['name'] ), | |
| 116 | + 'name' => $original_name, | |
| 93 | 117 | 'date_created' => $today->format( 'Y-m-d H:i:s' ), |
| 94 | 118 | ); |
| 95 | 119 | |
| 96 | - // Check for image type. Add a ".txt" extension to non-image file to prevent executing uploaded files on server. | |
| 97 | - $img_extensions = array( 'png', 'jpeg', 'jpg', 'bmp', 'pdf', 'gif' ); | |
| 98 | - if ( ! in_array( $extension, $img_extensions ) ) { | |
| 120 | + // List of safe image extensions. | |
| 121 | + $safe_images = array( 'png', 'jpeg', 'jpg', 'bmp', 'gif', 'webp' ); | |
| 122 | + | |
| 123 | + if ( in_array( $extension, $safe_images, true ) && ! wp_get_image_mime( $file['tmp_name'] ) ) { | |
| 124 | + wp_send_json_error( 'Invalid image file!', 400 ); | |
| 125 | + } | |
| 126 | + | |
| 127 | + // PDF validation. | |
| 128 | + if ( 'pdf' === $extension && 'application/pdf' !== $wp_filetype['type'] ) { | |
| 129 | + wp_send_json_error( 'Invalid PDF file!', 400 ); | |
| 130 | + } | |
| 131 | + | |
| 132 | + if ( in_array( $extension, $safe_images, true ) || 'pdf' === $extension ) { | |
| 133 | + $data['is_image'] = 1; | |
| 134 | + } else { | |
| 99 | 135 | $data['is_image'] = 0; |
| 100 | - } else { | |
| 101 | - $data['is_image'] = 1; | |
| 102 | 136 | } |
| 103 | 137 | |
| 104 | - // File path. | |
| 105 | - $file_path = $upload_dir['basedir'] . '/wpsc/' . $today->format( 'Y' ) . '/' . $today->format( 'm' ); | |
| 106 | - if ( ! file_exists( $file_path ) ) { | |
| 107 | - mkdir( $file_path, 0755, true ); | |
| 108 | - } | |
| 109 | - $file_path .= '/' . $filename; | |
| 138 | + $ext = pathinfo( $filename, PATHINFO_EXTENSION ); | |
| 139 | + $file_name = substr( $filename, 0, -( strlen( $ext ) + 1 ) ); | |
| 140 | + $filename = $file_name . '.' . strtolower( $ext ); | |
| 110 | 141 | |
| 111 | - $filepath_short = '/wpsc/' . $today->format( 'Y' ) . '/' . $today->format( 'm' ) . '/' . $filename; | |
| 112 | - $data['file_path'] = $filepath_short; | |
| 142 | + $filepath_short = '/wpsc/' . $today->format( 'Y' ) . '/' . $today->format( 'm' ) . '/' . $filename; | |
| 143 | + $data['file_path'] = $filepath_short; | |
| 113 | 144 | |
| 114 | - // Insert record in database. | |
| 115 | - if ( move_uploaded_file( $file['tmp_name'], $file_path ) ) { | |
| 145 | + $upload_overrides = array( | |
| 146 | + 'test_form' => false, | |
| 147 | + ); | |
| 116 | 148 | |
| 149 | + $file['name'] = $filename; | |
| 150 | + | |
| 151 | + add_filter( 'upload_dir', array( 'WPSC_Attachment', 'wpsc_upload_dir' ) ); | |
| 152 | + add_filter( 'upload_mimes', array( 'WPSC_Attachment', 'wpsc_custom_upload_mimes' ) ); | |
| 153 | + | |
| 154 | + $uploaded_file = wp_handle_upload( $file, $upload_overrides ); | |
| 155 | + | |
| 156 | + remove_filter( 'upload_dir', array( 'WPSC_Attachment', 'wpsc_upload_dir' ) ); | |
| 157 | + remove_filter( 'upload_mimes', array( 'WPSC_Attachment', 'wpsc_custom_upload_mimes' ) ); | |
| 158 | + | |
| 159 | + if ( $uploaded_file && empty( $uploaded_file['error'] ) ) { | |
| 160 | + | |
| 161 | + // Normalize path for cross-platform compatibility. | |
| 162 | + $normalized_path = wp_normalize_path( $uploaded_file['file'] ); | |
| 163 | + | |
| 164 | + $pos = strpos( $normalized_path, '/wpsc/' ); | |
| 165 | + if ( $pos === false ) { | |
| 166 | + wp_send_json_error( 'Something went wrong!', 500 ); | |
| 167 | + } | |
| 168 | + | |
| 169 | + $new_path = substr( $normalized_path, $pos ); | |
| 170 | + | |
| 171 | + if ( $new_path[0] !== '/' ) { | |
| 172 | + $new_path = '/' . $new_path; | |
| 173 | + } | |
| 174 | + | |
| 175 | + $data['file_path'] = $new_path; | |
| 176 | + | |
| 117 | 177 | $attachment = WPSC_Attachment::insert( $data ); |
| 118 | 178 | if ( ! $attachment->id ) { |
| 119 | - wp_send_json_error( 'Something went wrong, attachment record not created!', 500 ); | |
| 179 | + wp_send_json_error( 'Something went wrong!', 500 ); | |
| 120 | 180 | } |
| 121 | - $data = array( | |
| 122 | - 'id' => intval( $attachment->id ), | |
| 123 | - 'name' => $attachment->name, | |
| 181 | + | |
| 182 | + // Record who uploaded it so that only they can later bind it | |
| 183 | + // to a ticket. | |
| 184 | + WPSC_Attachment::set_uploader( $attachment ); | |
| 185 | + | |
| 186 | + return new WP_REST_Response( | |
| 187 | + array( | |
| 188 | + 'id' => intval( $attachment->id ), | |
| 189 | + 'name' => $attachment->name, | |
| 190 | + ), | |
| 191 | + 200 | |
| 124 | 192 | ); |
| 125 | - return new WP_REST_Response( $data, 200 ); | |
| 126 | 193 | } |
| 127 | 194 | |
| 128 | - wp_send_json_error( 'Something went wrong, file not saved!', 500 ); | |
| 195 | + $error_message = isset( $uploaded_file['error'] ) ? $uploaded_file['error'] : 'Something went wrong!'; | |
| 196 | + wp_send_json_error( $error_message, 500 ); | |
| 129 | 197 | } |
| 130 | 198 | |
| 131 | 199 | /** |
| 132 | 200 | * Single attachment |