From 44e4bc0a61f7f203710b269fb56fa1553536b44b Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 19 Jan 2012 12:36:38 -0500
Subject: [PATCH 1/3] Issue #1410588: Validate images before saving a node.

---
 plupload.module |  136 +++++++++++++++++++++++++++++++------------------------
 1 file changed, 76 insertions(+), 60 deletions(-)

diff --git a/plupload.module b/plupload.module
index 6dba8cd..16087b2 100644
--- a/plupload.module
+++ b/plupload.module
@@ -226,7 +226,9 @@ function plupload_pernode() {
 
   $image_node = plupload_imagefield_create_node_from($temp_directory . DIRECTORY_SEPARATOR . $file_name, $file_name, $options);
 
-  // @todo check the $image_node and do some error handling.
+  if (!$image_node) {
+    die('{"jsonrpc" : "2.0", "error" : {"code": 104, "message": "Image appears to be corrupt."}, "id" : "id"}');
+  }
 
   // Return JSON-RPC response
   die('{"jsonrpc" : "2.0", "result" : null, "id" : "id"}');
@@ -236,76 +238,90 @@ function plupload_pernode() {
 /**
  * Helper wrapper around the cck code to create a node of this type.
  * Largely copied from imagefield_import.module
+ *
  * @param $temp_filename
- *   string path to the file
+ *   String path to the file.
  * @param $name
- *   string name to use for the file
+ *   String name to use for the file.
  * @return $node
- *   a node object.
+ *   A node object, or FALSE if an error occured.
  */
 function plupload_imagefield_create_node_from($temp_filepath, $file_name, $options) {
+  $node = FALSE;
   // Only get files from Drupal's tmp directory.
   $directory = file_directory_temp();
-  if (file_check_location($temp_filepath, $directory)) {
-    // Only get files where we can get some image info.
-    if ($info = image_get_info($temp_filepath)) {
-      // Let the options array override the variable if it's set
-      // The drupal_ prefix is removed at this point
-      if (isset($options['plupload_import_field_type'])) {
-        $plupload_import_field_type = $options['plupload_import_field_type'];
-        unset($options['plupload_import_field_type']);
-      }
-      else {
-        $plupload_import_field_type = variable_get('plupload_import_field_type', 'photo:::field_photo');
+  // Only get files where we can get some image info.
+  if (file_check_location($temp_filepath, $directory) && $info = image_get_info($temp_filepath)) {
+
+    // image_get_info() is a very basic check and only looks at the metadata of
+    // the image. If imageapi is available, we use this to ensure that the
+    // image isn't corrupt. Otherwise, core doesn't provide an easy way to get
+    // the function name for a normal image toolkit. So, we assume that if the
+    // site isn't using imageapi, it's likely just using the default GD
+    // toolkit.
+    if (module_exists('imageapi') && !imageapi_image_open($temp_filepath)) {
+      return $node;
+    }
+    elseif (function_exists('image_gd_open') && !image_gd_open($temp_filepath)) {
+      return $node;
+    }
+
+    // Let the options array override the variable if it's set
+    // The drupal_ prefix is removed at this point
+    if (isset($options['plupload_import_field_type'])) {
+      $plupload_import_field_type = $options['plupload_import_field_type'];
+      unset($options['plupload_import_field_type']);
+    }
+    else {
+      $plupload_import_field_type = variable_get('plupload_import_field_type', 'photo:::field_photo');
+    }
+    // Figure out which node and field to put this into.
+    list($type, $field_name) = split(':::', $plupload_import_field_type);
+
+    // Get the field and its validators.
+    $field = content_fields($field_name, $type);
+    $validators = imagefield_widget_upload_validators($field);
+
+    // make sure that the directory exists
+    $directory = filefield_widget_file_path($field);
+    field_file_check_directory($directory, FILE_CREATE_DIRECTORY);
+
+    // Create some defaults that imagefield expects.
+    $form_state_values = array(
+      'title' => $file_name,
+      'body' => '',
+      'field_photo' => array(0 => array(
+          'fid' => 0,
+          'list' => '1',
+          'filepath' => '',
+          'filename' => '',
+          'filemime' => '',
+          'filesize' => 0,
+          'filefield_upload' => 'Upload',
+          'filefield_remove' => 'Remove',
+          'upload' => 0,
+        ),
+      'node_status' => NULL,
+      )
+    );
+    // Save the file and create a node.
+    if ($file = field_file_save_file($temp_filepath, $validators, $directory)) {
+      $file['original_path'] = $temp_filepath;
+
+      // Add the description if the settings call for it (it can be turned off).
+      if ($field['description_field'] === '1') {
+        $file['data']['description'] = '';
       }
-      // Figure out which node and field to put this into.
-      list($type, $field_name) = split(':::', $plupload_import_field_type);
-
-      // Get the field and its validators.
-      $field = content_fields($field_name, $type);
-      $validators = imagefield_widget_upload_validators($field);
-
-      // make sure that the directory exists
-      $directory = filefield_widget_file_path($field);
-      field_file_check_directory($directory, FILE_CREATE_DIRECTORY);
-
-      // Create some defaults that imagefield expects.
-      $form_state_values = array(
-        'title' => $file_name,
-        'body' => '',
-        'field_photo' => array(0 => array(
-            'fid' => 0,
-            'list' => '1',
-            'filepath' => '',
-            'filename' => '',
-            'filemime' => '',
-            'filesize' => 0,
-            'filefield_upload' => 'Upload',
-            'filefield_remove' => 'Remove',
-            'upload' => 0,
-          ),
-        'node_status' => NULL,
-        )
-      );
-      // Save the file and create a node.
-      if ($file = field_file_save_file($temp_filepath, $validators, $directory)) {
-        $file['original_path'] = $temp_filepath;
-
-        // Add the description if the settings call for it (it can be turned off).
-        if ($field['description_field'] === '1') {
-          $file['data']['description'] = '';
-        }
 
-        // Add the default alt and title text if configured to have defaults (they're always 'on').
-        $file['data']['alt'] = isset($field['widget']['alt']) ? $field['widget']['alt']: '';
-        $file['data']['title'] = isset($field['widget']['title']) ? $field['widget']['title']: '';
+      // Add the default alt and title text if configured to have defaults (they're always 'on').
+      $file['data']['alt'] = isset($field['widget']['alt']) ? $field['widget']['alt']: '';
+      $file['data']['title'] = isset($field['widget']['title']) ? $field['widget']['title']: '';
 
-        // Set list status to default.
-        $file['list'] = $field['list_field'] === '1' && $field['list_default'] === 0 ? '0' : '1';
+      // Set list status to default.
+      $file['list'] = $field['list_field'] === '1' && $field['list_default'] === 0 ? '0' : '1';
 
-        $node = _plupload_imagefield_import_create_node($field, $form_state_values, $file, $options);
-        file_delete($temp_filepath);
-      }
+      $node = _plupload_imagefield_import_create_node($field, $form_state_values, $file, $options);
+      file_delete($temp_filepath);
 
     }
   }
-- 
1.7.9.2


From 30b37be1e50ae5f1a62a28e3aa332b3e53093dee Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 19 Jan 2012 14:19:59 -0500
Subject: [PATCH 2/3] Issue #1410588: Show any errors returned when uploading
 images.

---
 plupload.css |    9 ++++++++-
 plupload.js  |   28 +++++++++++++++++++++++++---
 2 files changed, 33 insertions(+), 4 deletions(-)

diff --git a/plupload.css b/plupload.css
index 2b6ccec..ed89bbb 100644
--- a/plupload.css
+++ b/plupload.css
@@ -5,4 +5,11 @@ div.plupload_header h3 {
   color: #FFFFFF;
   text-align:center;
   padding: 1em;
-}
\ No newline at end of file
+}
+
+div.plupload_header ul {
+  background-color: #45494D;
+  color: #FFFFFF;
+  padding: 0.5em 1em 1em 1.3em;
+}
+
diff --git a/plupload.js b/plupload.js
index 20ab563..e56fb00 100644
--- a/plupload.js
+++ b/plupload.js
@@ -21,23 +21,45 @@ Drupal.behaviors.pluploadBuild = function (context) {
 Drupal.behaviors.pluploadSuccess = function (context) {
   var totalUploadFiles = 0;
   var upload = $('#uploader').pluploadQueue();               
+  var error = $('<ul></ul>');
+
   upload.bind('FileUploaded', function(up, file, res) {
+    // We can't rely on the status key as the page callback uses die(). For
+    // now, we have to manually decode the json response and check for any errors.
+    var status = {};
+    if (typeof JSON == 'object') {
+      status = JSON.parse(res.response);
+    }
+    else {
+      status = eval('(' + res.response + ')');
+    }
+
+    if (status.error) {
+      error.append('<li>' + Drupal.t("There was an error uploading %file: %message", {'%file': file.name, '%message': status.error.message}) + '</li>');
+    }
+
     totalUploadFiles--;
     if(totalUploadFiles == 0) {
       
       var count = $('#uploader').pluploadQueue().total.uploaded;
       
-      var successText = Drupal.formatPlural(count, 'Success! 1 image uploaded.', 'Success! @count images uploaded.');
-      
+      var finishedText = "";
+      if (error.children().size() == 0) {
+        finishedText = '<h3>' + Drupal.formatPlural(count, 'Success! 1 image uploaded.', 'Success! @count images uploaded.') + '</h3>';
+      }
+      else {
+        finishedText = error;
+      }
       
       $('div.plupload_header').slideUp('slow', function() {
-        $('div.plupload_header').html('<h3>'+ successText +'</h3>').slideDown('slow');
+        $('div.plupload_header').html(finishedText).slideDown('slow');
       });
     }
   });
 
   upload.bind('QueueChanged', function(up, files) {
     totalUploadFiles = upload.files.length;
+    error = $('<ul></ul>');
   });
 
 };
-- 
1.7.9.2


From 989366ce06efadc4bcbd598a81262600a7203174 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Mon, 5 Mar 2012 15:33:55 -0500
Subject: [PATCH 3/3] Issue #1410588: Log the browser and upload runtime when
 an error occurs.

---
 plupload.js     |    5 +++++
 plupload.module |    1 +
 2 files changed, 6 insertions(+)

diff --git a/plupload.js b/plupload.js
index e56fb00..20030b9 100644
--- a/plupload.js
+++ b/plupload.js
@@ -16,6 +16,11 @@ Drupal.behaviors.pluploadBuild = function (context) {
       {title : "Image files", extensions : Drupal.settings.plupload.extensions}
     ]
    });
+
+  // Add the runtime we are using so that we can log it with errors in the
+  // watchdog.
+  var upload = $('#uploader').pluploadQueue();
+  upload.settings.url = upload.settings.url + "&runtime=" + upload.runtime;
 };
 
 Drupal.behaviors.pluploadSuccess = function (context) {
diff --git a/plupload.module b/plupload.module
index 16087b2..7444172 100644
--- a/plupload.module
+++ b/plupload.module
@@ -227,6 +227,7 @@ function plupload_pernode() {
   $image_node = plupload_imagefield_create_node_from($temp_directory . DIRECTORY_SEPARATOR . $file_name, $file_name, $options);
 
   if (!$image_node) {
+    watchdog('plupload', 'A corrupt image was uploaded and was unable to be saved. The image was uploaded with %browser using the %method upload method.', array('%browser' => $_SERVER['HTTP_USER_AGENT'], '%method' => $_REQUEST['runtime']), WATCHDOG_WARNING);
     die('{"jsonrpc" : "2.0", "error" : {"code": 104, "message": "Image appears to be corrupt."}, "id" : "id"}');
   }
 
-- 
1.7.9.2

