Index: trac/attachment.py
===================================================================
--- trac/attachment.py	(revision 9435)
+++ trac/attachment.py	(working copy)
@@ -594,14 +594,9 @@
 
     # Internal methods
 
-    def _do_save(self, req, attachment):
+    def _do_save_one(self, req, attachment, upload, description):
         req.perm(attachment.resource).require('ATTACHMENT_CREATE')
 
-        if 'cancel' in req.args:
-            req.redirect(get_resource_url(self.env, attachment.resource.parent,
-                                          req.href))
-
-        upload = req.args['attachment']
         if not hasattr(upload, 'filename') or not upload.filename:
             raise TracError(_('No file uploaded'))
         if hasattr(upload.file, 'fileno'):
@@ -629,7 +624,7 @@
             raise TracError(_('No file uploaded'))
         # Now the filename is known, update the attachment resource
         # attachment.filename = filename
-        attachment.description = req.args.get('description', '')
+        attachment.description = description
         attachment.author = get_reporter_id(req, 'author')
         attachment.ipnr = req.remote_addr
 
@@ -667,6 +662,29 @@
             attachment.filename = None
         attachment.insert(filename, upload.file, size)
 
+    def _do_save_many(self,req, attachment):
+        curr_attach = 0
+    while curr_attach < int(req.args['multiple_attachments']):
+        upload=req.args['attachment-%d' % curr_attach]
+        if hasattr(upload, 'filename') and upload.filename:
+            attachment.filename = None
+        self._do_save_one(req, attachment, upload, req.args.get('description-%d' % curr_attach, ''))
+        curr_attach=curr_attach+1
+
+    def _do_save(self, req, attachment):
+        req.perm(attachment.resource).require('ATTACHMENT_CREATE')
+
+        if 'cancel' in req.args:
+            req.redirect(get_resource_url(self.env, attachment.resource.parent,
+                                          req.href))
+
+    if 'multiple_attachments' in req.args:
+        self._do_save_many(req, attachment)
+        req.redirect(get_resource_url(self.env, attachment.resource(id=None),
+                                      req.href))
+    else:
+        self._do_save_one(req, attachment, req.args['attachment'], req.args.get('description', ''))
+        # Redirect the user to list of attachments (must add a trailing '/')
         req.redirect(get_resource_url(self.env, attachment.resource(id=None),
                                       req.href))
 
Index: trac/htdocs/js/trac.js
===================================================================
--- trac/htdocs/js/trac.js	(revision 9435)
+++ trac/htdocs/js/trac.js	(working copy)
@@ -105,3 +105,36 @@
   }
 
 })(jQuery);
+
+var ATTACHFILE_COUNTER=0;
+function manageMultipleAttachFields(_obj){
+    if (_obj.value == '') {
+        if($("#multiAttach_tbody").get(0).rows.length == 1) return;
+      $("#multiAttach_tbody").get(0).deleteRow($(_obj).attr("attachnum")*1);
+      ATTACHFILE_COUNTER=0;
+      $("#multiAttach_tbody tr").each(function(index, element){
+          $("input", $("td", element).get(0))
+              .attr("attachnum",ATTACHFILE_COUNTER)
+              .get(0).name = "attachment-"+ATTACHFILE_COUNTER;
+          $("input", $("td", element).get(1))
+              .get(0).name = "description-"+ATTACHFILE_COUNTER;
+          ATTACHFILE_COUNTER++;
+      });
+      $("#multiAttach_count").get(0).value = $("#multiAttach_count").val()*1-1;
+    } else {
+      if ($(_obj).attr("attachnum") != $("#multiAttach_count").val()-1) return;
+      var tr = $("#multiAttach_tbody").get(0).insertRow(-1);
+      var td = tr.insertCell(-1);
+      $('<input type="file" onchange="manageMultipleAttachFields(this)">')
+         .attr("attachnum",$("#multiAttach_count").val())
+         .appendTo(td)
+         .get(0).name = "attachment-"+$("#multiAttach_count").val();
+
+      td = tr.insertCell(-1);
+      $('<input type="text">')
+          .attr("size",60)
+          .appendTo(td)
+          .get(0).name = "description-"+$("#multiAttach_count").val();
+      $("#multiAttach_count").get(0).value = $("#multiAttach_count").val()*1+1;
+    }
+}
Index: trac/templates/attachment.html
===================================================================
--- trac/templates/attachment.html	(revision 9435)
+++ trac/templates/attachment.html	(working copy)
@@ -24,15 +24,43 @@
   </head>
 
   <body>
-    <div py:choose="mode" id="content" class="attachment">
-      <py:when test="'new'">
-        <h1>Add Attachment to <a href="${url_of(parent)}">${name_of(parent)}</a></h1>
-        <form id="attachment" method="post" enctype="multipart/form-data" action="">
+    <py:def function="attach_oneFile()">
+      <div class="field">
+        <label>File:<br /><input type="file" name="attachment" /></label>
+      </div>
+      <fieldset>
+        <legend>Attachment Info</legend>
+        <py:if test="authname == 'anonymous'">
           <div class="field">
-            <label>File<py:if test="max_size >= 0"> (size limit
-                  ${pretty_size(max_size)})</py:if>:<br />
-              <input type="file" name="attachment" /></label>
+            <label>Your email or username:<br />
+              <input type="text" name="author" size="30" value="$author" />
+            </label>
           </div>
+          </py:if>
+        <div class="field">
+          <label>Description of the file (optional):<br />
+            <input type="text" name="description" size="60" /></label>
+        </div>
+        <br />
+        <div class="options">
+          <label><input type="checkbox" name="replace" />
+            Replace existing attachment of the same name</label>
+        </div>
+        <br />
+      </fieldset>
+    </py:def>
+    <py:def function="attach_multiFile()">
+     <table><thead>
+          <tr><th align="left">File</th><th align="left">Description</th></tr>
+     </thead>
+    <tbody id="multiAttach_tbody">
+        <tr>
+            <td><input type="file" attachnum="0" name="attachment-0" onchange="manageMultipleAttachFields(this);"/></td>
+            <td><input type="text" name="description-0" size="60" /></td>
+        </tr>
+        </tbody></table>
+        <input type="hidden" id="multiAttach_count" name="multiple_attachments" value="1" />
+
           <fieldset>
             <legend>Attachment Info</legend>
             <py:if test="authname == 'anonymous'">
@@ -42,19 +70,22 @@
                 </label>
               </div>
               </py:if>
-            <div class="field">
-              <label>Description of the file (optional):<br />
-                <input type="text" name="description" size="60" /></label>
-            </div>
-            <br />
             <py:if test="authname and authname != 'anonymous'">
               <div class="options">
                 <label><input type="checkbox" name="replace" />
-                  Replace existing attachment of the same name</label>
+                  Replace existing attachments of the same name</label>
               </div>
               <br />
             </py:if>
           </fieldset>
+    </py:def>
+
+    <div py:choose="mode" id="content" class="attachment">
+      <py:when test="'new'">
+        <h1>Add Attachment to <a href="${url_of(parent)}">${name_of(parent)}</a></h1>
+        <form id="attachment" method="post" enctype="multipart/form-data" action="">
+        
+        ${attach_multiFile()}
           <div class="buttons">
             <input type="hidden" name="action" value="new" />
             <input type="hidden" name="realm" value="$parent.realm" />

