diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index 0bcea2eceaf3c18c7c3a45229103ec84e4cb0b84..decfdbd2cda0136789b609c3ad057e49d9d409cd 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -91,6 +91,11 @@
 	position: relative;
 }
 
+/* fit app list view heights */
+.app-files #app-content>.viewcontainer {
+	height: 100%;
+}
+
 /**
  * Override global #controls styles
  * to be more flexible / relative
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 68b222071441c849a2bfd9aabe9f782a0d81f214..1b2a62137e5ec6d3a17dd468d7040dc01edfb076 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -1529,13 +1529,18 @@
 			fileUploadStart.on('fileuploaddrop', function(e, data) {
 				OC.Upload.log('filelist handle fileuploaddrop', e, data);
 
-				var dropTarget = $(e.originalEvent.target).closest('tr, .crumb');
-				// check if dropped inside this list at all
-				if (dropTarget && !self.$el.has(dropTarget).length) {
+				var dropTarget = $(e.originalEvent.target);
+				// check if dropped inside this container and not another one
+				if (dropTarget.length && !self.$el.is(dropTarget) && !self.$el.has(dropTarget).length) {
 					return false;
 				}
 
-				if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder
+				// find the closest tr or crumb to use as target
+				dropTarget = dropTarget.closest('tr, .crumb');
+
+				// if dropping on tr or crumb, drag&drop upload to folder
+				if (dropTarget && (dropTarget.data('type') === 'dir' ||
+					dropTarget.hasClass('crumb'))) {
 
 					// remember as context
 					data.context = dropTarget;
@@ -1555,7 +1560,7 @@
 					}
 
 					// update folder in form
-					data.formData = function(form) {
+					data.formData = function() {
 						return [
 							{name: 'dir', value: dir},
 							{name: 'requesttoken', value: oc_requesttoken},
@@ -1563,6 +1568,9 @@
 						];
 					};
 				} else {
+					// we are dropping somewhere inside the file list, which will
+					// upload the file to the current directory
+
 					// cancel uploads to current dir if no permission
 					var isCreatable = (self.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
 					if (!isCreatable) {
diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php
index 8cab4ce220b864523a5b898fb98383574aa7b306..b52effb1e7808dadb5776cc4848f129f29ccd75b 100644
--- a/apps/files/templates/index.php
+++ b/apps/files/templates/index.php
@@ -2,7 +2,7 @@
 <?php $_['appNavigation']->printPage(); ?>
 <div id="app-content">
 	<?php foreach ($_['appContents'] as $content) { ?>
-	<div id="app-content-<?php p($content['id']) ?>" class="hidden">
+	<div id="app-content-<?php p($content['id']) ?>" class="hidden viewcontainer">
 	<?php print_unescaped($content['content']) ?>
 	</div>
 	<?php } ?>
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index 3e9950dfe19fc60c57c4f2a8492156bb113bf02e..855a5c9af5188aef27c6ff5c9246bc3602369416 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -64,6 +64,8 @@ describe('OCA.Files.FileList tests', function() {
 			'   <div class="actions creatable"></div>' +
 			'   <div class="notCreatable"></div>' +
 			'</div>' +
+			// uploader
+			'<input type="file" id="file_upload_start" name="files[]" multiple="multiple">' +
 			// dummy table
 			// TODO: at some point this will be rendered by the fileList class itself!
 			'<table id="filestable">' +
@@ -1686,4 +1688,139 @@ describe('OCA.Files.FileList tests', function() {
 			expect(fileList.findFileEl('Three.pdf').index()).toEqual(0);
 		});
 	});
+	/**
+	 * Test upload mostly by testing the code inside the event handlers
+	 * that were registered on the magic upload object
+	 */
+	describe('file upload', function() {
+		var $uploader;
+
+		beforeEach(function() {
+			// note: this isn't the real blueimp file uploader from jquery.fileupload
+			// but it makes it possible to simulate the event triggering to
+			// test the response of the handlers
+			$uploader = $('#file_upload_start');
+			fileList.setupUploadEvents();
+			fileList.setFiles(testFiles);
+		});
+
+		afterEach(function() {
+			$uploader = null;
+		});
+
+		describe('dropping external files', function() {
+			var uploadData;
+
+			/**
+			 * Simulate drop event on the given target
+			 *
+			 * @param $target target element to drop on
+			 * @return event object including the result
+			 */
+			function dropOn($target, data) {
+				var eventData = {
+					originalEvent: {
+						target: $target
+					}
+				};
+				var ev = new $.Event('fileuploaddrop', eventData);
+				// using triggerHandler instead of trigger so we can pass
+				// extra data
+				$uploader.triggerHandler(ev, data || {});
+				return ev;
+			}
+
+			/**
+			 * Convert form data to a flat list
+			 * 
+			 * @param formData form data array as used by jquery.upload
+			 * @return map based on the array's key values
+			 */
+			function decodeFormData(data) {
+				var map = {};
+				_.each(data.formData(), function(entry) {
+					map[entry.name] = entry.value;
+				});
+				return map;
+			}
+
+			beforeEach(function() {
+				// simulate data structure from jquery.upload
+				uploadData = {
+					files: [{
+						relativePath: 'fileToUpload.txt'
+					}]
+				};
+			});
+			afterEach(function() {
+				uploadData = null;
+			});
+			it('drop on a tr or crumb outside file list does not trigger upload', function() {
+				var $anotherTable = $('<table><tbody><tr><td>outside<div class="crumb">crumb</div></td></tr></table>');
+				var ev;
+				$('#testArea').append($anotherTable);
+				ev = dropOn($anotherTable.find('tr'), uploadData);
+				expect(ev.result).toEqual(false);
+
+				ev = dropOn($anotherTable.find('.crumb'));
+				expect(ev.result).toEqual(false);
+			});
+			it('drop on an element outside file list container does not trigger upload', function() {
+				var $anotherEl = $('<div>outside</div>');
+				var ev;
+				$('#testArea').append($anotherEl);
+				ev = dropOn($anotherEl);
+
+				expect(ev.result).toEqual(false);
+			});
+			it('drop on an element inside the table triggers upload', function() {
+				var ev;
+				ev = dropOn(fileList.$fileList.find('th:first'), uploadData);
+
+				expect(ev.result).not.toEqual(false);
+			});
+			it('drop on an element on the table container triggers upload', function() {
+				var ev;
+				ev = dropOn($('#app-content-files'), uploadData);
+
+				expect(ev.result).not.toEqual(false);
+			});
+			it('drop on an element inside the table does not trigger upload if no upload permission', function() {
+				$('#permissions').val(0);
+				var ev;
+				ev = dropOn(fileList.$fileList.find('th:first'));
+
+				expect(ev.result).toEqual(false);
+			});
+			it('drop on a file row inside the table triggers upload to current folder', function() {
+				var ev;
+				ev = dropOn(fileList.findFileEl('One.txt').find('td:first'), uploadData);
+
+				expect(ev.result).not.toEqual(false);
+			});
+			it('drop on a folder row inside the table triggers upload to target folder', function() {
+				var ev, formData;
+				ev = dropOn(fileList.findFileEl('somedir').find('td:eq(2)'), uploadData);
+
+				expect(ev.result).not.toEqual(false);
+				expect(uploadData.formData).toBeDefined();
+				formData = decodeFormData(uploadData);
+				expect(formData.dir).toEqual('/subdir/somedir');
+				expect(formData.file_directory).toEqual('fileToUpload.txt');
+				expect(formData.requesttoken).toBeDefined();
+			});
+			it('drop on a breadcrumb inside the table triggers upload to target folder', function() {
+				var ev, formData;
+				fileList.changeDirectory('a/b/c/d');
+				ev = dropOn(fileList.$el.find('.crumb:eq(2)'), uploadData);
+
+				expect(ev.result).not.toEqual(false);
+				expect(uploadData.formData).toBeDefined();
+				formData = decodeFormData(uploadData);
+				expect(formData.dir).toEqual('/a/b');
+				expect(formData.file_directory).toEqual('fileToUpload.txt');
+				expect(formData.requesttoken).toBeDefined();
+			});
+		});
+	});
 });