diff --git a/components/file.inc b/components/file.inc
index c28e2a1..89ff3b7 100644
--- a/components/file.inc
+++ b/components/file.inc
@@ -18,11 +18,11 @@ function _webform_defaults_file() {
     'extra' => array(
       'filtering' => array(
         'types' => array('gif', 'jpg', 'png'),
-        'addextensions' => '',
-        'size' => 800,
+        'size' => '2 MB',
       ),
-      'savelocation' => '',
-      'width' => '',
+      'scheme' => 'public',
+      'directory' => '',
+      'progress_indicator' => 'throbber',
       'title_display' => 0,
       'description' => '',
       'attributes' => array(),
@@ -40,10 +40,6 @@ function _webform_theme_file() {
       'render element' => 'form',
       'file' => 'components/file.inc',
     ),
-    'webform_render_file' => array(
-      'render element' => 'element',
-      'file' => 'components/file.inc',
-    ),
     'webform_display_file' => array(
       'render element' => 'element',
       'file' => 'components/file.inc',
@@ -57,6 +53,8 @@ function _webform_theme_file() {
 function _webform_edit_file($component) {
     $form = array();
     $form['#theme'] = 'webform_edit_file';
+    $form['#element_validate'] = array('_webform_edit_file_check_directory');
+    $form['#after_build'] = array('_webform_edit_file_check_directory');
 
     $form['validation']['filtering'] = array(
       '#element_validate' => array('_webform_edit_file_filtering_validate'),
@@ -120,24 +118,52 @@ function _webform_edit_file($component) {
       '#type' => 'textfield',
       '#title' => t('Max Upload Size'),
       '#default_value' => $component['extra']['filtering']['size'],
-      '#description' => t('Enter the max file size a user may upload (in KB).'),
+      '#description' => t('Enter the max file size a user may upload such as 2 MB or 800 KB. Your server has a max upload size of @size.', array('@size' => format_size(file_upload_max_size()))),
       '#size' => 10,
       '#weight' => 3,
-      '#field_suffix' => t('KB'),
       '#default_value' => $component['extra']['filtering']['size'],
       '#parents' => array('extra', 'filtering', 'size'),
       '#element_validate' => array('_webform_edit_file_size_validate'),
     );
-    $form['extra']['savelocation'] = array(
+
+    $scheme_options = array();
+    foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $stream_wrapper) {
+      $scheme_options[$scheme] = $stream_wrapper['name'];
+    }
+    $form['extra']['scheme'] = array(
+      '#type' => 'radios',
+      '#title' => t('Upload destination'),
+      '#options' => $scheme_options,
+      '#default_value' => $component['extra']['scheme'],
+      '#description' => t('Private file storage has significantly more overhead than public files, but restricts file access to users who can view submissions.'),
+      '#weight' => 4,
+      '#access' => count($scheme_options) > 1,
+    );
+    $form['extra']['directory'] = array(
       '#type' => 'textfield',
-      '#title' => t('Upload Directory'),
-      '#default_value' => $component['extra']['savelocation'],
-      '#description' => t('Webform uploads are always saved in the site files directory. You may optionally specify a subfolder to store your files.'),
-      '#weight' => 3,
-      '#field_prefix' => file_stream_wrapper_get_instance_by_scheme(file_default_scheme())->getDirectoryPath() . '/webform/',
-      '#element_validate' => array('_webform_edit_file_check_directory'),
-      '#after_build' => array('_webform_edit_file_check_directory'),
+      '#title' => t('Upload directory'),
+      '#default_value' => $component['extra']['directory'],
+      '#description' => t('You may optionally specify a sub-directory to store your files.'),
+      '#weight' => 5,
+      '#field_prefix' => 'webform/',
+    );
+
+    $form['display']['progress_indicator'] = array(
+      '#type' => 'radios',
+      '#title' => t('Progress indicator'),
+      '#options' => array(
+        'throbber' => t('Throbber'),
+        'bar' => t('Bar with progress meter'),
+      ),
+      '#default_value' => $component['extra']['progress_indicator'],
+      '#description' => t('The throbber display does not show the status of uploads but takes up less space. The progress bar is helpful for monitoring progress on large uploads.'),
+      '#weight' => 16,
+      '#access' => file_progress_implementation(),
+      '#parents' => array('extra', 'progress_indicator'),
     );
+
+    // TODO: Make managed_file respect the "size" parameter.
+    /*
     $form['display']['width'] = array(
       '#type' => 'textfield',
       '#title' => t('Width'),
@@ -148,16 +174,25 @@ function _webform_edit_file($component) {
       '#weight' => 4,
       '#parents' => array('extra', 'width')
     );
+    */
+
     return $form;
 }
 
 /**
- * A Form API element validate function to check filesize is numeric.
+ * A Form API element validate function to check filesize is valid.
  */
 function _webform_edit_file_size_validate($element) {
   if (!empty($element['#value'])) {
-    if (!is_numeric($element['#value']) || intval($element['#value']) != $element['#value']) {
-      form_error($element, t('Max upload size must be a number in KB.'));
+    $set_filesize = parse_size($element['#value']);
+    if ($set_filesize == FALSE) {
+      form_error($element, t('File size @value is not a valid filesize. Use a value such as 2 MB or 800 KB.', array('@value' => $element['#value'])));
+    }
+    else {
+      $max_filesize = parse_size(file_upload_max_size());
+      if ($max_filesize < $set_filesize) {
+        form_error($element, t('An upload size of @value is too large, you are allow to upload files @max or less.', array('@value' => $element['#value'], '@max' => format_size($max_filesize))));
+      }
     }
   }
 }
@@ -168,17 +203,19 @@ function _webform_edit_file_size_validate($element) {
  * Ensure that the destination directory exists and is writable.
  */
 function _webform_edit_file_check_directory($element) {
-  $base_dir = file_build_uri('webform');
-  $destination_dir = file_build_uri('webform/' . $element['#value']);
+  $scheme = $element['extra']['scheme']['#value'];
+  $directory = $element['extra']['directory']['#value'];
+
+  $destination_dir = file_stream_wrapper_uri_normalize($scheme . '://' . $directory . '/webform');
 
   // Sanity check input to prevent use parent (../) directories.
   if (preg_match('/\.\.[\/\\\]/', $destination_dir . '/')) {
-    form_error($element, t('The save directory %directory is not valid.', array('%directory' => $destination_dir)));
+    form_error($element['extra']['directory'], t('The save directory %directory is not valid.', array('%directory' => $directory)));
   }
   else {
     $destination_success = file_prepare_directory($destination_dir, FILE_CREATE_DIRECTORY);
     if (!$destination_success) {
-      form_error($element, t('The save directory %directory could not be created. Check that the webform files directory is writtable.', array('%directory' => $destination_dir)));
+      form_error($element['extra']['directory'], t('The save directory %directory could not be created. Check that the webform files directory is writable.', array('%directory' => $directory)));
     }
   }
 
@@ -304,214 +341,53 @@ function theme_webform_edit_file($variables) {
  * Implements _webform_render_component().
  */
 function _webform_render_file($component, $value = NULL, $filter = TRUE) {
-  // Normally every file component is given a unique ID based on its key.
-  if (isset($component['nid'])) {
-    $node = node_load($component['nid']);
-    $form_key = implode('_', webform_component_parent_keys($node, $component));
-  }
-  // If being used as a default though, we don't yet have a form key.
-  else {
-    $form_key = 'default';
+  // Cap the upload size according to the PHP limit.
+  $max_filesize = parse_size(file_upload_max_size());
+  $set_filesize = $component['extra']['filtering']['size'];
+  if (!empty($set_filesize) && parse_size($set_filesize) < $max_filesize) {
+    $max_filesize = parse_size($set_filesize);
   }
 
-  $element[$form_key] = array(
-    '#type' => 'file',
-    //'#required' => $component['mandatory'], // Drupal core bug with required file uploads.
+  $element = array(
+    '#type' => 'managed_file',
+    '#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
+    '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
+    '#required' => $component['mandatory'],
+    '#default_value' => isset($value[0]) ? $value[0] : NULL,
     '#attributes' => $component['extra']['attributes'],
-    '#tree' => FALSE, // file_check_upload assumes a flat $_FILES structure.
-    '#element_validate' => array(
-      '_webform_validate_file',
-      '_webform_required_file',  // Custom required routine.
+    '#upload_validators' => array(
+      'file_validate_size' => array($max_filesize),
+      'file_validate_extensions' => array(implode(' ', $component['extra']['filtering']['types'])),
     ),
+    '#pre_render' => array_merge(element_info_property('managed_file', '#pre_render'), array('webform_file_allow_access')),
+    '#upload_location' => $component['extra']['scheme'] . '://webform/' . $component['extra']['directory'],
+    '#progress_indicator' => $component['extra']['progress_indicator'],
+    '#description' => $filter ? _webform_filter_descriptions($component['extra']['description']) : $component['extra']['description'],
+    '#weight' => $component['weight'],
+    '#theme_wrappers' => array('webform_element'),
     '#webform_component' => $component,
-    '#theme_wrappers' => array(),
-  );
-  $element['#webform_component'] = $component;
-  $element['#weight'] = $component['weight'];
-  $element['#title'] = $filter ? _webform_filter_xss($component['name']) : $component['name'];
-  $element['#title_display'] = $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before';
-  $element['#description'] = $filter ? _webform_filter_descriptions($component['extra']['description']) : $component['extra']['description'];
-
-  $element['#theme'] = 'webform_render_file';
-  $element['#theme_wrappers'] = array('webform_element');
-
-  // Change the 'width' option to the correct 'size' option.
-  if ($component['extra']['width'] > 0) {
-    $element[$form_key]['#size'] = $component['extra']['width'];
-  }
-
-  // Handles the asterisk for mandatory fields. Note that this is only on the
-  // wrapper, not on the file field itself. We handle validation separately.
-  if ($component['mandatory']) {
-    $element['#required'] = TRUE;
-  }
-
-  // Add a hidden element to store the FID for new files.
-  $element['_fid'] = array(
-    '#type' => 'hidden',
-    '#default_value' => '',
-  );
-
-  // Add a hidden element to store the FID for existing files.
-  $element['_old'] = array(
-    '#type' => 'hidden',
-    '#value' => isset($value[0]) ? $value[0] : NULL,
   );
 
   return $element;
 }
 
 /**
- * Render a File component.
- */
-function theme_webform_render_file($variables) {
-  $element = $variables['element'];
-
-  // Add information about the existing file, if any.
-  if (isset($element['#default_value'])) {
-    $element['_fid']['#value'] = $element['#default_value'];
-  }
-  $value = $element['_fid']['#value'] ? $element['_fid']['#value'] : $element['_old']['#value'];
-
-  if ($value && ($file = webform_get_file($value))) {
-    $firstchild = array_shift(array_keys($element));
-    $element[$firstchild]['#suffix'] = ' ' . l(t('Download @filename', array('@filename' => webform_file_name($file->uri))), webform_file_url($file->uri)) . (isset($element['#suffix']) ? $element['#suffix'] : '');
-    $element[$firstchild]['#description'] = '<div class="webform-newfile-message">' . t('Uploading a new file will replace the current file.') . '</div>' . (isset($element[$firstchild]['#description']) ? $element[$firstchild]['#description'] : '');
-  }
-
-  return drupal_render_children($element);
-}
-
-/**
- * A Form API element validate function.
+ * Pre-render callback to allow access to uploaded files.
  *
- * Fix Drupal core's handling of required file fields.
- */
-function _webform_required_file($element, $form_state) {
-  $component = $element['#webform_component'];
-  $parents = $element['#array_parents'];
-  array_pop($parents);
-  $form_key = implode('_', $parents);
-
-  // Do not validate requiredness on back or draft button.
-  if (isset($form_state['clicked_button']['#validate']) && empty($form_state['clicked_button']['#validate'])) {
-    return;
-  }
-
-  // Check if a value is already set in the hidden field.
-  $values = $form_state['values'];
-  $key = array_shift($parents);
-  $found = FALSE;
-  while (isset($values[$key])) {
-    if (isset($values[$key])) {
-      $values = $values[$key];
-      $found = TRUE;
-    }
-    else {
-      $found = FALSE;
-    }
-    $key = array_shift($parents);
-  }
-
-  if (!$found || (empty($values['_fid']) && empty($values['_old']))) {
-    if (empty($_FILES['files']['name'][$form_key]) && $component['mandatory']) {
-      form_error($element, t('%field field is required.', array('%field' => $component['name'])));
-    }
-  }
-}
-
-/**
- * A Form API element validate function.
+ * Files that have not yet been saved into a submission must be accessible to
+ * the user who uploaded it, but no one else. After the submission is saved,
+ * access is granted through the file_usage table. Before then, we use a
+ * $_SESSION value to record a user's upload.
  *
- * Ensure that the uploaded file matches the specified file types.
+ * @see webform_file_download()
  */
-function _webform_validate_file($element, &$form_state) {
-  $component = $element['#webform_component'];
-  $form_key = implode('_', $element['#parents']);
-
-  if (empty($_FILES['files']['name'][$form_key]) || form_get_error($element)) {
-    return;
-  }
-
-  // Build a human readable list of extensions:
-  $extensions = $component['extra']['filtering']['types'];
-  $extension_list = '';
-  if (count($extensions) > 1) {
-    for ($n = 0; $n < count($extensions) - 1; $n++) {
-      $extension_list .= $extensions[$n] . ', ';
-    }
-    $extension_list .= 'or ' . $extensions[count($extensions) - 1];
-  }
-  elseif (!empty($extensions)) {
-    $extension_list = $extensions[0];
+function webform_file_allow_access($element) {
+  if (!empty($element['#value']['fid'])) {
+    $fid = $element['#value']['fid'];
+    $_SESSION['webform_files'][$fid] = $fid;
   }
 
-  if (in_array('jpg', $extensions)) {
-    $extensions[] = 'jpeg';
-  }
-
-  $strrpos = function_exists('mb_strrpos') ? 'mb_strrpos' : 'strrpos';
-  $dot = $strrpos($_FILES['files']['name'][$form_key], '.');
-  $extension = drupal_strtolower(drupal_substr($_FILES['files']['name'][$form_key], $dot+1));
-  $file_error = FALSE;
-  if (!empty($extensions) && !in_array($extension, $extensions)) {
-    form_error($element, t("Files with the '%ext' extension are not allowed, please upload a file with a %exts extension.", array('%ext' => $extension, '%exts' => $extension_list)));
-    $file_error = TRUE;
-  }
-
-  // Now let's check the file size (limit is set in KB).
-  if ($_FILES['files']['size'][$form_key] > $component['extra']['filtering']['size'] * 1024) {
-    form_error($element, t("The file '%filename' is too large (%filesize KB). Please upload a file %maxsize KB or smaller.", array('%filename' => $_FILES['files']['name'][$form_key], '%filesize' => (int) ($_FILES['files']['size'][$form_key]/1024), '%maxsize' => $component['extra']['filtering']['size'])));
-    $file_error = TRUE;
-  }
-
-  // Save the file to a temporary location.
-  if (!$file_error) {
-    $upload_dir = file_build_uri('webform/' . $component['extra']['savelocation']);
-    if (file_prepare_directory($upload_dir, FILE_CREATE_DIRECTORY)) {
-      // Note that the $extensions list was already validated above but needs to
-      // be passed into file_save_upload() for it to pass internal validation
-      // and not use the default extension checklist.
-      $file = file_save_upload($form_key, array('file_validate_extensions' => array(implode(' ', $extensions))), $upload_dir);
-      if ($file) {
-        // Set the hidden field value.
-        $parents = $element['#array_parents'];
-        array_pop($parents);
-        $parents[] = '_fid';
-        form_set_value(array('#parents' => $parents), $file->fid, $form_state);
-      }
-      else {
-        drupal_set_message(t('The uploaded file %filename was unable to be saved. The destination directory may not be writable.', array('%filename' => $file->filename)), 'error');
-      }
-    }
-    else {
-      drupal_set_message(t('The uploaded file was unable to be saved. The destination directory does not exist.'), 'error');
-    }
-  }
-}
-
-/**
- * Implements _webform_submit_component().
- */
-function _webform_submit_file($component, $value) {
-
-  if ($value['_fid'] && ($file = webform_get_file($value['_fid']))) {
-    // Save any new files permanently.
-    $file->status = FILE_STATUS_PERMANENT;
-    file_save($file);
-
-    // Delete any previous files.
-    if ($value['_old'] && $value['_old'] != $value['_fid'] && ($existing = webform_get_file($value['_old']))) {
-      file_delete($existing);
-    }
-
-    $value = $file->fid;
-  }
-  else {
-    $value = $value['_old'] ? $value['_old'] : NULL;
-  }
-
-  return $value;
+  return $element;
 }
 
 /**
@@ -646,3 +522,22 @@ function webform_get_file($fid) {
   // system error.
   return $fid ? file_load($fid) : FALSE;
 }
+
+/**
+ * Given a submission with file_usage set, add or remove file usage entries.
+ */
+function webform_file_usage_adjust($submission) {
+  if (isset($submission->file_usage)) {
+    $files = file_load_multiple($submission->file_usage['added_fids']);
+    foreach ($files as $file) {
+      file_usage_add($file, 'webform', 'submission', $submission->sid);
+    }
+
+    $files = file_load_multiple($submission->file_usage['deleted_fids']);
+    foreach ($files as $file) {
+      file_usage_delete($file, 'webform', 'submission', $submission->sid);
+      file_delete($file);
+    }
+  }
+}
+
diff --git a/css/webform.css b/css/webform.css
index 9e1b22b..036d199 100644
--- a/css/webform.css
+++ b/css/webform.css
@@ -21,3 +21,10 @@ html.js input.webform-calendar {
 .webform-container-inline div.description {
   display: block;
 }
+.webform-container-inline div.messages {
+  display: block;
+  float: left;
+}
+.webform-container-inline div.ajax-progress-bar div {
+  display: inherit;
+}
diff --git a/webform.install b/webform.install
index a80b24e..8723892 100644
--- a/webform.install
+++ b/webform.install
@@ -717,3 +717,96 @@ function webform_update_7316() {
     db_add_field('webform', 'total_submit_interval', array('type' => 'int', 'not null' => TRUE, 'default' => -1));
   }
 }
+
+/**
+ * Upgrade file components to support the new AJAX-upload element.
+ */
+function webform_update_7317() {
+  $result = db_select('webform_component', 'wc', array('fetch' => PDO::FETCH_ASSOC))
+    ->fields('wc')
+    ->condition('type', 'date')
+    ->execute();
+  foreach ($result as $component) {
+    $component['extra'] = unserialize($component->extra);
+    if (!isset($component['extra']['directory'])) {
+      $component['extra']['directory'] = $component['extra']['savelocation'];
+      $component['extra']['scheme'] = file_default_scheme();
+      $component['extra']['size'] = $component['extra']['size'] . ' KB';
+      unset($component['extra']['filtering']['addextensions']);
+      unset($component['extra']['savelocation']);
+      $component['extra'] = serialize($component['extra']);
+      drupal_write_record('webform_component', $component, array('nid', 'cid'));
+    }
+  }
+
+  return t('File components updated to support AJAX uploading.');
+}
+
+/**
+ * Add file usage entries for all files uploaded through Webform.
+ */
+function webform_update_7318(&$sandbox) {
+  if (!isset($sandbox['progress'])) {
+    // Initialize batch update information.
+    $sandbox['progress'] = 0;
+    $sandbox['last_fid_processed'] = -1;
+    $sandbox['max'] = db_select('file_managed')
+      ->condition('uri', '%' . db_like('://webform/') . '%', 'LIKE')
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+  }
+
+  // Process all files attached to a given revision during the same batch.
+  $limit = variable_get('webform_update_batch_size', 100);
+  $files = db_select('file_managed', 'f')
+    ->fields('f')
+    ->condition('uri', '%' . db_like('://webform/') . '%', 'LIKE')
+    ->condition('fid', $sandbox['last_fid_processed'], '>')
+    ->orderBy('fid', 'ASC')
+    ->range(0, $limit)
+    ->execute()
+    ->fetchAllAssoc('fid', PDO::FETCH_ASSOC);
+
+  // Determine each submission with which a file is associated.
+  if (!empty($files)) {
+    foreach ($files as $fid => $file) {
+      $file = (object) $file;
+      $sids = db_query('SELECT wsd.sid FROM {webform_component} wc INNER JOIN {webform_submitted_data} wsd ON wc.nid = wsd.nid AND wc.type = :file WHERE data = :fid', array(':file' => 'file', ':fid' => $file->fid))->fetchAllAssoc('sid', PDO::FETCH_ASSOC);
+      foreach ($sids as $sid => $row) {
+        // We use a db_merge() instead of file_usage_add() to prevent problems
+        // in the event this update was run twice. No file provided by Webform
+        // should ever be in use more than once at this point.
+        db_merge('file_usage')
+          ->key(array(
+            'fid' => $file->fid,
+            'type' => 'submission',
+            'module' => 'webform',
+            'id' => $sid,
+          ))
+          ->fields(array(
+            'count' => 1,
+          ))
+          ->execute();
+      }
+
+      // Update our progress information for the batch update.
+      $sandbox['progress']++;
+      $sandbox['last_fid_processed'] = $file->fid;
+    }
+  }
+
+  // If less than limit was processed, the update process is finished.
+  if (count($files) < $limit || $sandbox['progress'] == $sandbox['max']) {
+    $finished = TRUE;
+  }
+
+  // If there's no max value then there's nothing to update and we're finished.
+  if (empty($sandbox['max']) || isset($finished)) {
+    return t('Webform file entries created in the file_usage table.');
+  }
+  else {
+    // Indicate our current progress to the batch update system.
+    $sandbox['#finished'] = $sandbox['progress'] / $sandbox['max'];
+  }
+}
diff --git a/webform.module b/webform.module
index 0c4ff15..c8e86d9 100644
--- a/webform.module
+++ b/webform.module
@@ -888,46 +888,102 @@ function webform_webform_submission_actions($node, $submission) {
 }
 
 /**
+ * Implements hook_webform_submission_update().
+ *
+ * We implement our own hook here to facilitate the File component, which needs
+ * to clean up manage file usage records and delete files from submissions that
+ * have been edited if necessary.
+ */
+function webform_webform_submission_presave($node, &$submission) {
+  // Check if there are any file components in this submission and if any of
+  // them currently contain files.
+  $has_file_components = FALSE;
+  $new_fids = array();
+  $old_fids = array();
+
+  foreach ($node->webform['components'] as $cid => $component) {
+    if ($component['type'] == 'file') {
+      $has_file_components = TRUE;
+      if (!empty($submission->data[$cid]['value'])) {
+        $new_fids = array_merge($new_fids, $submission->data[$cid]['value']);
+      }
+    }
+  }
+
+  if ($has_file_components) {
+    // If we're updating a submission, build a list of previous files.
+    if (isset($submission->sid)) {
+      $old_submission = webform_get_submission($node->nid, $submission->sid, TRUE);
+
+      foreach ($node->webform['components'] as $cid => $component) {
+        if ($component['type'] == 'file') {
+          if (!empty($old_submission->data[$cid]['value'])) {
+            $old_fids = array_merge($old_fids, $old_submission->data[$cid]['value']);
+          }
+        }
+      }
+    }
+
+    // Save the list of added or removed files so we can add usage in
+    // hook_webform_submission_insert() or _update().
+    $submission->file_usage = array(
+      // Diff the old against new to determine what files were deleted.
+      'deleted_fids' => array_diff($old_fids, $new_fids),
+      // Diff the new files against old to determine new uploads.
+      'added_fids' => array_diff($new_fids, $old_fids)
+    );
+  }
+}
+
+/**
+ * Implements hook_webform_submission_insert().
+ */
+function webform_webform_submission_insert($node, $submission) {
+  if (isset($submission->file_usage)) {
+    webform_component_include('file');
+    webform_file_usage_adjust($submission);
+  }
+}
+
+/**
+ * Implements hook_webform_submission_update().
+ */
+function webform_webform_submission_update($node, $submission) {
+  if (isset($submission->file_usage)) {
+    webform_component_include('file');
+    webform_file_usage_adjust($submission);
+  }
+}
+
+/**
  * Implements hook_file_download().
  *
  * Only allow users with view webform submissions to download files.
  */
 function webform_file_download($uri) {
-  global $user;
+  module_load_include('inc', 'webform', 'includes/webform.submissions');
 
-  // Determine whether this file was a webform upload. If it was, retrieve file
-  // information, plus the user id of the uploader.
-  $file = db_query("SELECT ws.uid, f.* FROM {file_managed} f INNER JOIN {webform_submitted_data} wsd ON f.fid = wsd.data INNER JOIN {webform_submissions} ws ON ws.sid = wsd.sid INNER JOIN {webform_component} wc ON wc.cid = wsd.cid  WHERE f.uri = :uri AND wc.type = :file", array('uri' => $uri, ':file' => 'file'))->fetchObject();
-  if ($file) {
-    // Allow file access for admins, or for users who are viewing their own
-    // submissions.
-    if (user_access('access all webform results') || ($user->uid == $file->uid && user_access('access own webform results'))) {
-      // TODO: This is a copy/paste from file_file_download. Switch to using
-      // file_get_content_headers() instead if http://drupal.org/node/943112
-      // gets committed.
-      $name = mime_header_encode($file->filename);
-      $type = mime_header_encode($file->filemime);
-      // Serve images, text, and flash content for display rather than download.
-      $inline_types = variable_get('file_inline_types', array('^text/', '^image/', 'flash$'));
-      $disposition = 'attachment';
-      foreach ($inline_types as $inline_type) {
-        // Exclamation marks are used as delimiters to avoid escaping slashes.
-        if (preg_match('!' . $inline_type . '!', $file->filemime)) {
-          $disposition = 'inline';
-        }
-      }
-      return array(
-        'Content-Type' => $type . '; name="' . $name . '"',
-        'Content-Length' => $file->filesize,
-        'Content-Disposition' => $disposition . '; filename="' . $name . '"',
-        'Cache-Control' => 'private',
-      );
+  // Determine whether this file was a webform upload.
+  $row = db_query("SELECT fu.id as sid, f.fid FROM {file_managed} f LEFT JOIN {file_usage} fu ON f.fid = fu.fid AND fu.module = :webform AND fu.type = :submission WHERE f.uri = :uri", array('uri' => $uri, ':webform' => 'webform', ':submission' => 'submission'))->fetchObject();
+  if ($row) {
+    $file = file_load($row->fid);
+  }
+  if (!empty($file->sid)) {
+    $submissions = webform_get_submissions(array('sid' => $row->sid));
+    $submission = reset($submissions);
+  }
+
+  // Grant access based on access to the submission.
+  if (!empty($submission)) {
+    $node = node_load($submission->nid);
+    if (webform_submission_access($node, $submission)) {
+      return file_get_content_headers($file);
     }
-    // This is a webform-controlled file, but the user doesn't have access.
-    return -1;
   }
-  // This is not a webform-controlled file.
-  return NULL;
+  // Grant access to files uploaded by a user before the submission is saved.
+  elseif (!empty($file) && !empty($_SESSION['webform_files'][$file->fid])) {
+    return file_get_content_headers($file);
+  }
 }
 
 /**
