diff --git a/file.php b/file.php
index 994b77a0c2256a0218a27ffebb01b1c7d77a344b..00193f7a0b4b810964d9388a2a9a893f00406907 100644
--- a/file.php
+++ b/file.php
@@ -28,10 +28,11 @@ if (!$_GET['key']
 
 // Get the object type the file is attached to
 $type = '';
+$attachment = null;
 if ($_GET['id']
-        && ($a=$file->attachments->findFirst(array(
+        && ($attachment=$file->attachments->findFirst(array(
                     'id' => $_GET['id']))))
-    $type = $a->type;
+    $type = $attachment->type;
 
 // Enforce security settings if enabled.
 if ($cfg->isAuthRequiredForFiles()
@@ -62,7 +63,9 @@ if ($file->verifySignature($_GET['signature'], $_GET['expires'])) {
             return $file->display($s);
 
         // Download the file..
-        $file->download(@$_GET['disposition'] ?: false, $_GET['expires']);
+        $filename = $attachment ? $attachment->name : $file->getName();
+        $disposition = @$_GET['disposition'] ?: false;
+        $file->download($filename, $disposition, @$_GET['expires']);
     }
     catch (Exception $ex) {
         Http::response(500, 'Unable to find that file: '.$ex->getMessage());
diff --git a/include/ajax.tasks.php b/include/ajax.tasks.php
index 4cc99a0d614f569e06ff1f43c6ae84303f3abe40..cd85cf613cbf696280ae79182946d6c9e6b46fea 100644
--- a/include/ajax.tasks.php
+++ b/include/ajax.tasks.php
@@ -113,7 +113,7 @@ class TasksAjaxAPI extends AjaxController {
                 if ($desc
                         && $desc->isAttachmentsEnabled()
                         && ($attachments=$desc->getWidget()->getAttachments()))
-                    $vars['cannedattachments'] = $attachments->getClean();
+                    $vars['files'] = $attachments->getFiles();
                 $vars['staffId'] = $thisstaff->getId();
                 $vars['poster'] = $thisstaff;
                 $vars['ip_address'] = $_SERVER['REMOTE_ADDR'];
@@ -811,9 +811,9 @@ class TasksAjaxAPI extends AjaxController {
             switch ($_POST['a']) {
             case 'postnote':
                 $vars = $_POST;
-                $attachments = $note_form->getField('attachments')->getClean();
-                $vars['cannedattachments'] = array_merge(
-                    $vars['cannedattachments'] ?: array(), $attachments);
+                $attachments = $note_form->getField('attachments')->getFiles();
+                $vars['files'] = array_merge(
+                    $vars['files'] ?: array(), $attachments);
                 if(($note=$task->postNote($vars, $errors, $thisstaff))) {
                     $msg=__('Note posted successfully');
                     // Clear attachment list
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index a529c193862912a97be84195e512f1bbaa320710..2864efda74a09e4c5ceed50e699f15edbff6f5ef 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -1408,7 +1408,7 @@ function refer($tid, $target=null) {
                 if ($desc
                         && $desc->isAttachmentsEnabled()
                         && ($attachments=$desc->getWidget()->getAttachments()))
-                    $vars['cannedattachments'] = $attachments->getClean();
+                    $vars['files'] = $attachments->getFiles();
                 $vars['staffId'] = $thisstaff->getId();
                 $vars['poster'] = $thisstaff;
                 $vars['ip_address'] = $_SERVER['REMOTE_ADDR'];
@@ -1490,9 +1490,9 @@ function refer($tid, $target=null) {
             $vars = $_POST;
             switch ($_POST['a']) {
             case 'postnote':
-                $attachments = $note_attachments_form->getField('attachments')->getClean();
-                $vars['cannedattachments'] = array_merge(
-                    $vars['cannedattachments'] ?: array(), $attachments);
+                $attachments = $note_attachments_form->getField('attachments')->getFiles();
+                $vars['files'] = array_merge(
+                    $vars['files'] ?: array(), $attachments);
                 if (($note=$task->postNote($vars, $errors, $thisstaff))) {
                     $msg=__('Note posted successfully');
                     // Clear attachment list
@@ -1506,9 +1506,9 @@ function refer($tid, $target=null) {
                 }
                 break;
             case 'postreply':
-                $attachments = $reply_attachments_form->getField('attachments')->getClean();
-                $vars['cannedattachments'] = array_merge(
-                    $vars['cannedattachments'] ?: array(), $attachments);
+                $attachments = $reply_attachments_form->getField('attachments')->getFiles();
+                $vars['files'] = array_merge(
+                    $vars['files'] ?: array(), $attachments);
                 if (($response=$task->postReply($vars, $errors))) {
                     $msg=__('Update posted successfully');
                     // Clear attachment list
diff --git a/include/class.attachment.php b/include/class.attachment.php
index 9c6cd7c40b6e980b1e387e341b135ca9840f0082..bb5e8e292f11aa0342f82299f7d2b5c1bec04dd8 100644
--- a/include/class.attachment.php
+++ b/include/class.attachment.php
@@ -60,6 +60,10 @@ class Attachment extends VerySimpleModel {
         return $this->name ?: $this->file->name;
     }
 
+    function getName() {
+        return $this->getFilename();
+    }
+
     function getHashtable() {
         return $this->ht;
     }
@@ -133,7 +137,7 @@ extends InstrumentedList {
         foreach ($files as $file) {
             if (is_numeric($file))
                 $fileId = $file;
-            elseif (is_array($file) && isset($file['id']) && $file['id'] != 0)
+            elseif (is_array($file) && isset($file['id']) && $file['id'])
                 $fileId = $file['id'];
             elseif (isset($file['tmp_name']) && ($F = AttachmentFile::upload($file)))
                 $fileId = $F->getId();
diff --git a/include/class.canned.php b/include/class.canned.php
index 21cd9aa6367f2b4ec81959d1790177fbb751ad65..388420c5f541a7667f7d3c33a020df5000a68b36 100644
--- a/include/class.canned.php
+++ b/include/class.canned.php
@@ -138,7 +138,7 @@ extends VerySimpleModel {
 
                 $resp['files'] = array();
                 foreach ($this->getAttachedFiles(!$html) as $file) {
-                    $_SESSION[':cannedFiles'][$file->id] = 1;
+                    $_SESSION[':cannedFiles'][$file->id] = $file->name;
                     $resp['files'][] = array(
                         'id' => $file->id,
                         'name' => $file->name,
diff --git a/include/class.file.php b/include/class.file.php
index d880840861b09065a34cd275ebb726773d82e105..e61b5afe76516fcecfd880359bb6193c13164b4e 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -248,7 +248,7 @@ class AttachmentFile extends VerySimpleModel {
         return hash_hmac('sha1', implode("\n", $pieces), SECRET_SALT);
     }
 
-    function download($disposition=false, $expires=false) {
+    function download($name=false, $disposition=false, $expires=false) {
         $disposition = $disposition ?: 'inline';
         $bk = $this->open();
         if ($bk->sendRedirectUrl($disposition))
@@ -258,7 +258,7 @@ class AttachmentFile extends VerySimpleModel {
         $type = $this->getType() ?: 'application/octet-stream';
         if (isset($_REQUEST['overridetype']))
             $type = $_REQUEST['overridetype'];
-        Http::download($this->getName(), $type, null, 'inline');
+        Http::download($name ?: $this->getName(), $type, null, 'inline');
         header('Content-Length: '.$this->getSize());
         $this->sendData(false);
         exit();
diff --git a/include/class.forms.php b/include/class.forms.php
index 760ea76d051175783a240ac1a03e1a9912457e87..c9985089b344b1c8836f91ed596f13f47d1436d8 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -3402,6 +3402,10 @@ class FileUploadField extends FormField {
                 if ($a && ($f=$a->getFile()))
                     $files[] = $f;
             }
+
+            foreach (@$this->getClean() as $key => $value)
+                $files[] = array('id' => $key, 'name' => $value);
+
             $this->files = $files;
         }
         return $this->files;
@@ -3472,9 +3476,7 @@ class FileUploadField extends FormField {
     }
 
     function parse($value) {
-        // Values in the database should be integer file-ids
-        return array_map(function($e) { return (int) $e; },
-            $value ?: array());
+        return $value;
     }
 
     function to_php($value) {
@@ -4464,6 +4466,7 @@ class FileUploadWidget extends Widget {
             $F = AttachmentFile::objects()
                 ->filter(array('id__in' => array_keys($new)))
                 ->all();
+
             foreach ($F as $f) {
                 $f->tmp_name = $new[$f->getId()];
                 $files[] = array(
@@ -4512,7 +4515,7 @@ class FileUploadWidget extends Widget {
             foreach (AttachmentFile::format($_FILES[$this->name]) as $file) {
                 try {
                     $F = $this->field->uploadFile($file);
-                    $ids[] = $F->getId();
+                    $ids[$F->getId()] = $F->getName();
                 }
                 catch (FileUploadError $ex) {}
             }
@@ -4523,17 +4526,17 @@ class FileUploadWidget extends Widget {
         // identified in the session
         //
         // If no value was sent, assume an empty list
-        if (!($_files = parent::getValue()))
+        if (!($files = parent::getValue()))
             return array();
 
         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-            foreach ($_files as $info) {
-                if (@list($id,$name) = explode(',', $info, 2))
-                    $files[$id] = $name;
+            $_files = array();
+            foreach ($files as $info) {
+                if (@list($id, $name) = explode(',', $info, 2))
+                    $_files[$id] = $name;
             }
+            $files = $_files;
         }
-        else
-          $files = $_files;
 
         $allowed = array();
         // Files already attached to the field are allowed
@@ -4556,7 +4559,7 @@ class FileUploadWidget extends Widget {
                 continue;
 
             // Keep the values as the IDs
-            $ids[$id] = $name ?: $allowed[$id] ?: $id;
+            $ids[$id] = $name;
         }
 
         return $ids;
diff --git a/include/class.thread.php b/include/class.thread.php
index 59d864060708003ca72a0439fef703752551fc62..481a34835aed3ce179a8c83bf8e07b3ed3b84596 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -1084,10 +1084,9 @@ implements TemplateVariable {
         $ids = array();
         foreach ($files as $id => $info) {
             $F = array('inline' => is_array($info) && @$info['inline']);
+            $AF = null;
 
-            if (is_numeric($id) && $id != 0)
-                $fileId = $id;
-            elseif ($info instanceof AttachmentFile)
+            if ($info instanceof AttachmentFile)
                 $fileId = $info->getId();
             elseif (is_array($info) && isset($info['id']))
                 $fileId = $info['id'];
@@ -1095,8 +1094,8 @@ implements TemplateVariable {
                 $fileId = $AF->getId();
             elseif ($add_error) {
                 $error = $info['error']
-                    ?: sprintf(_S('Unable to import attachment - %s'),
-                        $id ?: $info['name']);
+                    ?: sprintf(_S('Unable to save attachment - %s'),
+                        $info['name'] ?: $info['id']);
                 if (is_numeric($error) && isset($error_descriptions[$error])) {
                     $error = sprintf(_S('Error #%1$d: %2$s'), $error,
                         _S($error_descriptions[$error]));
@@ -1157,11 +1156,13 @@ implements TemplateVariable {
         elseif (is_string($name)) {
             $filename = $name;
         }
+
         if ($filename) {
             // This should be a noop since the ORM caches on PK
             $F = @$file['file'] ?: AttachmentFile::lookup($file['id']);
             // XXX: This is not Unicode safe
-            if ($F && 0 !== strcasecmp($F->name, $filename))
+            // TODO: fix name lookup
+            if ($F && strcasecmp($F->name, $filename) !== 0)
                 $att->name = $filename;
         }
 
@@ -1578,7 +1579,7 @@ implements TemplateVariable {
         $attached_files = array();
         foreach (array(
             // Web uploads and canned attachments
-            $vars['files'], $vars['cannedattachments'],
+            $vars['files'],
             // Emailed or API attachments
             $vars['attachments'],
             // Inline images (attached to the draft)
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 550785e5c3ecc13fabf8f5799c19e678f1798678..43446e89e67ed43a885f61045ec3266f3f1efbc2 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -2837,9 +2837,9 @@ implements RestrictedAccess, Threadable, Searchable {
             return false;
         }
         $files = array();
-        foreach ($canned->attachments->getAll() as $file) {
-            $files[] = $file->file_id;
-            $_SESSION[':cannedFiles'][$file->file_id] = 1;
+        foreach ($canned->attachments->getAll() as $att) {
+            $files[] = array('id' => $att->file_id, 'name' => $att->getName());
+            $_SESSION[':cannedFiles'][$att->file_id] = $att->getName();
         }
 
         if ($cfg->isRichTextEnabled())
@@ -2852,7 +2852,7 @@ implements RestrictedAccess, Threadable, Searchable {
         $info = array('msgId' => $message instanceof ThreadEntry ? $message->getId() : 0,
                       'poster' => __('SYSTEM (Canned Reply)'),
                       'response' => $response,
-                      'cannedattachments' => $files
+                      'files' => $files
         );
         $errors = array();
         if (!($response=$this->postReply($info, $errors, false, false)))
@@ -4088,8 +4088,8 @@ implements RestrictedAccess, Threadable, Searchable {
         $vars['note'] = ThreadEntryBody::clean($vars['note']);
         $create_vars = $vars;
         $tform = TicketForm::objects()->one()->getForm($create_vars);
-        $create_vars['cannedattachments']
-            = $tform->getField('message')->getWidget()->getAttachments()->getClean();
+        $create_vars['files']
+            = $tform->getField('message')->getWidget()->getAttachments()->getFiles();
 
         if (!($ticket=self::create($create_vars, $errors, 'staff', false)))
             return false;
diff --git a/open.php b/open.php
index a507bc979515181f5d46eb035a8ccaf9ade92e59..ef1dde53285c8d406c3ba5406fcb3f52df151854 100644
--- a/open.php
+++ b/open.php
@@ -33,7 +33,7 @@ if ($_POST) {
     $messageField = $tform->getField('message');
     $attachments = $messageField->getWidget()->getAttachments();
     if (!$errors && $messageField->isAttachmentsEnabled())
-        $vars['cannedattachments'] = $attachments->getClean();
+        $vars['files'] = $attachments->getFiles();
 
     // Drop the draft.. If there are validation errors, the content
     // submitted will be displayed back to the user
diff --git a/scp/tasks.php b/scp/tasks.php
index aa8fc88793d858957ede515d756eac951e094c36..5375261863e375f72e088b9958b80967299383b3 100644
--- a/scp/tasks.php
+++ b/scp/tasks.php
@@ -48,7 +48,7 @@ if($_POST && !$errors):
         switch(strtolower($_POST['a'])):
         case 'postnote': /* Post Internal Note */
             $vars = $_POST;
-            $vars['cannedattachments'] = $note_attachments_form->getField('attachments')->getClean();
+            $vars['files'] = $note_attachments_form->getField('attachments')->getFiles();
 
             $wasOpen = ($task->isOpen());
             if(($note=$task->postNote($vars, $errors, $thisstaff))) {
@@ -76,7 +76,7 @@ if($_POST && !$errors):
             break;
         case 'postreply': /* Post an update */
             $vars = $_POST;
-            $vars['cannedattachments'] = $reply_attachments_form->getField('attachments')->getClean();
+            $vars['files'] = $reply_attachments_form->getField('attachments')->getFiles();
 
             $wasOpen = ($task->isOpen());
             if (($response=$task->postReply($vars, $errors))) {
diff --git a/scp/tickets.php b/scp/tickets.php
index e9c310f4fdd9e5cc2c136024a94e83ca1962a1ab..d100f38d7b1af8e1505301922754e5925bc264dc 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -149,7 +149,7 @@ if($_POST && !$errors):
                 $errors['err'] = __('Action denied. Contact admin for access');
             } else {
                 $vars = $_POST;
-                $vars['cannedattachments'] = $response_form->getField('attachments')->getClean();
+                $vars['files'] = $response_form->getField('attachments')->getFiles();
                 $vars['response'] = ThreadEntryBody::clean($vars['response']);
                 if(!$vars['response'])
                     $errors['response']=__('Response required');
@@ -211,7 +211,7 @@ if($_POST && !$errors):
             break;
         case 'postnote': /* Post Internal Note */
             $vars = $_POST;
-            $vars['cannedattachments'] = $note_form->getField('attachments')->getClean();
+            $vars['files'] = $note_form->getField('attachments')->getFiles();
             $vars['note'] = ThreadEntryBody::clean($vars['note']);
 
             if ($cfg->isTicketLockEnabled()) {
@@ -403,7 +403,7 @@ if($_POST && !$errors):
                     if ($vars['uid'] && !($user=User::lookup($vars['uid'])))
                         $vars['uid'] = 0;
 
-                    $vars['cannedattachments'] = $response_form->getField('attachments')->getClean();
+                    $vars['files'] = $response_form->getField('attachments')->getFiles();
 
                     if(($ticket=Ticket::open($vars, $errors))) {
                         $msg=__('Ticket created successfully');
diff --git a/tickets.php b/tickets.php
index fa88e5e55775b4aeff32dda04841b35eed6fc178..560b16f3dc963008e05aa8c610607b82992fee95 100644
--- a/tickets.php
+++ b/tickets.php
@@ -84,7 +84,7 @@ if ($_POST && is_object($ticket) && $ticket->getId()) {
                     'poster' => (string) $thisclient->getName(),
                     'message' => $_POST['message']
                     );
-            $vars['cannedattachments'] = $attachments->getClean();
+            $vars['files'] = $attachments->getFiles();
             if (isset($_POST['draft_id']))
                 $vars['draft_id'] = $_POST['draft_id'];