Commit 6d93645a authored by Leon Kessler's avatar Leon Kessler
Browse files

Issue #3248466 by Leon Kessler: Duplicate file entities when uploaded through CORS [DATA LOSS]

parent 8e0c9c83
Loading
Loading
Loading
Loading
+63 −22
Original line number Diff line number Diff line
@@ -69,8 +69,15 @@
              // Set progress bar to 100% in case the upload was so fast.
              Drupal.flysystemS3.setCorsUploadProgress($progressBar, 100, Drupal.t('Processing upload'));

              Drupal.flysystemS3.saveFile(signedFormData.url, file_obj)
              .fail(function() {
                Drupal.flysystemS3.setCorsUploadProgress($progressBar, 1, Drupal.t('Signing request failed. Trying secondary upload method...'));
                // Trigger the submit button to let normal AJAX process the upload.
                Drupal.file.triggerUploadButton(event);
              })
              .done(function(saveFileData) {
                // Add the fid for this file to array.
              uploadedFileFid.push(signedFormData.fid);
                uploadedFileFid.push(saveFileData.fid);

                // Post the results to Drupal if all files have been processed.
                var num_fids = uploadedFileFid.length;
@@ -88,6 +95,7 @@
                  // Trigger the submit button to let normal AJAX process the upload.
                  Drupal.file.triggerUploadButton(event);
                }
              })
            });
        });
      });
@@ -133,15 +141,24 @@
      }
    },

    /**
     * Retrieves S3 signature for CORS upload, and a unique filename.
     *
     * @name Drupal.flysystemS3.requestSignature
     *
     * @param {jQuery} $fileElement
     *   The file field element.
     * @param file
     *   The file object to be uploaded.
     */
    requestSignature: function($fileElement, file) {
      // Use the file object and ask Drupal to generate the appropriate signed
      // request for us.
      var signingPostData = {
        filename: file.name,
        filesize: file.size,
        filemime: file.type,
        acl: $fileElement.attr('data-s3-acl'),
        destination: $fileElement.attr('data-s3-destination')
        'filename': file.name,
        'Content-Type': file.type,
        'acl': $fileElement.attr('data-s3-acl'),
        'destination': $fileElement.attr('data-s3-destination')
      };

      // POST to Drupal which will return the required parameters for signing
@@ -153,6 +170,30 @@
      });
    },

    /**
     * Post file data to Drupal to genereate a file object.
     *
     * @param url
     *   The full file url, including streamwrapper.
     * @param file
     *   The file object.
     */
    saveFile: function(url, file) {
      var postData = {
        url: url,
        filename: file.name,
        filesize: file.size,
        filemime: file.type,
      };

      // POST to Drupal which will save the uploaded file to Drupal.
      return $.ajax({
        url: drupalSettings.path.baseUrl + 'flysystem-s3/cors-upload-save',
        data: postData,
        type: 'POST'
      });
    },

    /**
     * Update the CORS progress bar with a percent and an optional label.
     *
+8 −1
Original line number Diff line number Diff line
flysystem_s3.cors:
flysystem_s3.cors_sign:
  path: '/flysystem-s3/cors-upload-sign'
  defaults:
    _controller: 'Drupal\flysystem_s3\Controller\S3CorsUploadAjaxController::signRequest'
  requirements:
    _permission: 'use S3 CORS upload'
    _method: 'POST'
flysystem_s3.cors_save:
  path: '/flysystem-s3/cors-upload-save'
  defaults:
    _controller: 'Drupal\flysystem_s3\Controller\S3CorsUploadAjaxController::saveFile'
  requirements:
    _permission: 'use S3 CORS upload'
    _method: 'POST'
+33 −21
Original line number Diff line number Diff line
@@ -72,35 +72,21 @@ class S3CorsUploadAjaxController extends ControllerBase {

    $client = $adapter->getClient();
    $bucket = $adapter->getBucket();
    $destination = $adapter->applyPathPrefix(StreamWrapperManager::getTarget($post['destination']));
    $destination = $this->fileSystem->getDestinationFilename($post['destination'] . '/' . $post['filename'], FileSystemInterface::EXISTS_RENAME);

    // Apply the prefix to the URI and use it as a key in the POST request.
    $post['key'] = $adapter->applyPathPrefix(StreamWrapperManager::getTarget($destination));

    $options = [
      ['acl' => $post['acl']],
      ['bucket' => $bucket],
      ['starts-with', '$key', $destination . '/'],
      ['starts-with', '$key', $post['key']],
      ['starts-with', '$Content-Type', $post['Content-Type']],
    ];

    // Retrieve the file name and build the URI.
    // Destination does not contain a prefix as it is applied by the fly system.
    $uri = \Drupal::service('file_system')->createFilename($post['filename'], $post['destination']);
    // Apply the prefix to the URI and use it as a key in the POST request.
    $post['key'] = $adapter->applyPathPrefix(StreamWrapperManager::getTarget($uri));

    // Create a temporary file to return with a file ID in the response.
    $file = File::create([
      'uri' => $post['key'],
      'filesize' => $post['filesize'],
      'filename' => $post['filename'],
      'filemime' => $post['filemime'],
      'uid' => \Drupal::currentUser()->getAccount()->id(),
    ]);
    $file->save();

    // Remove values not necessary for the request to Amazon.
    unset($post['destination']);
    unset($post['filename']);
    unset($post['filemime']);
    unset($post['filesize']);

    // @todo Make this interval configurable.
    $expiration = '+5 hours';
@@ -110,9 +96,35 @@ class S3CorsUploadAjaxController extends ControllerBase {
    $data['attributes'] = $postObject->getFormAttributes();
    $data['inputs'] = $postObject->getFormInputs();
    $data['options'] = $options;
    $data['fid'] = $file->id();
    $data['url'] = $destination;

    return new JsonResponse($data);
  }

  /**
   * Request handler for /flysystem-s3/cors-upload-save.
   *
   * Create a file object after the file has been successfuly uploaded to S3.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   A JsonResponse with the newly created file id.
   */
  public function saveFile(Request $request) {
    $post = $request->request->all();
    // Create a temporary file to return with a file ID in the response.
    $file = File::create([
      'uri' => $post['url'],
      'filesize' => $post['filesize'],
      'filename' => $this->fileSystem->baseName($post['url']),
      'filemime' => $post['filemime'],
      'uid' => \Drupal::currentUser()->getAccount()->id(),
    ]);
    $file->save();

    return new JsonResponse(['fid' => $file->id()]);
  }

}